第一阶段
良好的编码规范
先根据表来创建domain包以及对象。
根据domain对象来创建dao包以及接⼝。 接⼝的⽅法(增删改查), 接⼝的⽅法需要规范。
⽣成实现类(先空实现)。
⽣成测试类。
测试类是 测试dao的,所以可以在测试类中先去创建⼀个dao对象。
实现类的某⼀个⽅法,实现⼀个,测试⼀个。
重构
重构(Refactoring)就是通过调整程序代码,改善软件的质量、性能,使程序的设计模式和架构更趋于合理,提⾼软件的可扩展性和可维护性。
day01
如何学编程
-
明确需求
-
分步骤实现以上需求
Java的三大平台
Java SE: Java标准平台,允许开发软件运行在电脑桌面上(用户需要下载,安装)
Java EE: Java企业平台,针对Web方向
Java ME: Java微型平台
Java虚拟机首先将编译好的字节码文件加载到内存(由类装载器完成),这个过程称为类加载。
jvm内存模型
- 方法区:线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量等
- Java虚拟机栈(栈区stack):基本数据类型分配在栈区,还有每个方法被执行的时候都会同时创建一个栈帧用于存储该方法的局部变量、操作栈、动态链接、方法出口等信息,当方法调用完毕,该方法的栈帧就被销毁了
- 堆区(heap):被所有线程共享的一块内存区域,在虚拟机启动时创建。引用数据类型的内存分配在该区。
- 每次使用new关键字,就表示在堆内存中开辟一块新的存储空间。
JDK和JRE概述
jvm: Java虚拟机,专门用于执行Java的字节码文件
jre: Java程序的运行环境,包含jvm +常用类库
jdk: Java程序的开发环境,包含jre + javac/java等编译运行的开发工具
Java语言的特点
1.简单易用 2.安全可靠 3.跨平台 4.面向对象 5.支持多线程 6.健壮性
day02
标识符
1.可以是字符,数字,下划线,$,不能以数字开头,不能有!等特殊符号
2.不能是jdk中的关键字,保留字
3.大小写敏感
4.见名知意,驼峰标识
Java数据类型
- 基本数据类型:整数,小数,字符,布尔类型
- 引用数据类型:数组,类(new),接口,字符串,枚举,注解
数据类型的自动提升
①对于byte,short,char类型,一般都会自动提升到int类型
②在进行运算时,多种数据类型进行运算,最终会提升到最高的数据类型
变量概述
1> 变量声明如果没有赋值,没有默认值
2>变量本质上是一块合适的内存空间,空间中的值是可以根据程序的需要变化
3>变量必须先声明后赋值再使用
局部变量和成员变量
-
定义的位置不一样
局部变量:在方法的内部
成员变量:在方法的外部,直接写在类当中
-
作用范围不一样
局部变量:只有在方法当中才可以使用
成员变量:整个类全部都可以通用
-
默认值不一样
局部变量:没有默认值,必须手动进行赋值
成员变量:如果没有默认值,会有默认值
-
内存位置不一样
局部变量:位于栈内存
成员变量:位于堆内存
-
生命周期不一样
局部变量:随着方法进栈而诞生,随着方法的出栈而消失
成员变量:随着对象创建而诞生,随着对象被垃圾回收而消失
自增和自减
后置i++ / i-- : 遇到i++ / i-- ,i先参与运算,运算后再自增1 /自减1
前置++i / --i : 遇到++i / --i ,i先自增1 / 自减1,然后再参与运算
比较权威的解释:
- a++表示取a的地址,把它的值装入寄存器,然后增加内存中的a的值
- ++a表示取a的地址,增加它的内容,然后把值放在寄存器中
day03
switch基本语法
- switch中的整形表达式必须是byte short char int,但一定不能是long
- case 后面的值一定要用常量
循环
注:1. 对于循环的次数不确定,优先用while循环,对于次数已经确定的,优先用for循环。
2.外层循环控制行,内层循环控制列,外层循环执行一次,内层循环就执行n次,只要涉及行列问题(二维问题) ==》循环嵌套
day04
Java的自动垃圾回收机制
(GC垃圾回收器)==>不需要手动的去控制内存的释放,当JVM内存资源不够用的时候,会自动地清理堆中没有被引用到的对象所占用的内存空间。
数组
把具有相同类型的多个常量或者变量值有序组织起来的一种数据形式。
数组的初始化
- 数组在定义后,数组必须初始化才能使用,初始化就是在堆内存中给数组开辟一块存储空间,然后为每一个元素赋上初始值。
- 数组的长度是固定的。
- 数组的变量名称是存储在栈空间的,该变量存储的是其引用的地址,当操作该变量时就是在操作对应内存地址里的数据。
NullPointerException:操作了一个尚未初始化或者没有分配内存空间的引用数据类型
day05
值传递
把实参的值复制一份给形参的过程称为值传递
**注:方法参数中如果是基本数据类型参数,传递的是值。
如果是引用数据类型参数,传递的是所引用堆空间的地址值。
return的作用
- 返回计算完的值给方法调用者
- 结束当前方法,由gc回收方法内存
- 交出方法控制权
方法概述
- 方法必须定义在类中,在Java中最小的程序单元是类,必须先有类
- 一个类中可以定义多个方法
- 方法和方法是平行的,不能在方法中定义另一个方法
- 方法的定义没有先后顺序
方法可变参数
- 方法的可变参数其底层就是一个一维数组类型
- 可变参数必须作为方法的最后一个参数
- 方法最多只有一个可变参数
方法重载
指的是在一个类中可以声明多个同名的方法,而方法中参数列表不同(参数类型、参数个数、参数类型的顺序其中一个条件满足即可)。调用这些同名的方法时,JVM会根据实际参数的不同绑定到不同的方法。
***注:***与方法的返回值无关
第二阶段
day01
变量的生命周期
- 成员变量: 存储在堆内存中,随着对象的销毁而销毁
- 局部变量:存储在栈内存中,随着所定义的方法的调用结束而销毁
类和对象概述
类用于描述多个对象的共同特征,它是对象的模板,是对某一类事物的抽象描述,专门用于创建多个同类型的对象,类可以看作是对象的数据类型
对象是类的一个具体实例,是独立的,唯一的个体,对象是数据的载体,同类型的对象都具有相同的特性(属性)和行为(方法),但持有的数据不同,地址也不相同。
封装的概念
把字段和方法放在一个独立的模块中(类),字段用于持有数据,方法用于操作这些数据
封装的好处
- 保证数据的安全性
- 提高组件的重用性,把公用功能放到一个类中,需要该功能直接调用即可
JavaBean规范
-
类必须使用public修饰
-
必须有一个公共的无参构造器
-
字段使用private修饰,每个字段提供一对getter和setter方法
注:如果字段是boolean类型的,此时是is方法,例如isXxx()
day02
构造器和setter方法
构造器:创建对象并设置初始成员变量,只能初始化一次
setter方法: 创建对象后再设置初始数据,可以设置多次
继承的好处
- 减少多个类中代码重复的问题,增强了代码的复用性
访问权限修饰符
private: 私有的,本类可见
默认: 友好的(friendly),同包可见,经常被称为包访问权限
protected: 受保护的,同包可见,子类也可见
public: 公共的,任何地方都可见
访问权限大小: private < 默认 < protected < public
子类继承父类
子类可以拥有父类的所有成员(字段和方法),但还要看访问权限修饰符,如果是私有的,不能直接访问,但可以通过getter和setter进行访问。
如果父类中的成员使用public和protected修饰,子类都能继承.
如果父类和子类在同一个包中,使用缺省访问修饰的成员,此时子类可以继承到
如果父类中的成员使用private修饰,子类继承不到。private只能在本类中访问
父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同
总:子类继承父类的非私有成员(字段和方法),构造器除外
***构造方法***不能被继承。
方法重写的规则
- 实例方法签名必须相同 (方法签名= 方法名 + 方法的参数列表)
- 子类方法的返回值类型是和父类方法的返回类型相同或者是其子类
- 子类方法的访问权限 >= 父类方法访问权限
- 子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型
注:重写建立在继承的基础上,没有继承,就没有重写
抽象类特点
- 抽象类不能创建对象
- 抽象类中可以同时拥有抽象方法和普通方法
- 抽象类要有子类才有意义,子类必须覆盖父类的所有抽象方法,除非子类也是抽象类
== 符号的比较
- 基本数据类型: 比较两个值是否相等
- 引用数据类型: 比较两个对象是否是同一块内存空间,比较地址值
在实际开发过程中,需要比较两个对象字段的值是否相等,所以需重写equals方法,用equals方法进行比较
day03
接口
- 接口是一种规范,实现类必须实现该接口中的所有抽象方法(除非该实现类是抽象类)
- 一个实现类可以实现多个接口(遵循多个规范)
- 一个接口可以有多个实现类
- 一个实现类可以继承一个父类,实现一个或多个接口
接口约定了实现类应该具备的功能
多态
当编译时类型和运行时类型不一致时,就会产生多态,多态一定是建立在重写的基础上的,具体点就是当通过同一引用类型变量,由于引用的实例不同,对同一行为产生的结果不同。
多态:成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。
注 : 编译类型必须是运行类型的父类或接口
多态时方法调用执行流程
多态的条件(继承)
- 继承
- 覆盖(重写)
- 父类引用指向子类对象
多态的好处
提高了程序的可拓展性和可维护性,屏蔽了不同实现类之间的实现差异,从而可以做到通用编程。
接口和抽象类的区别
相同点:
- 都可以定义抽象方法
- 都不能创建对象
- 子类继承抽象父类必须实现抽象父类中的所有抽象方法;实现类实现接口必须实现接口中所有的抽象方法
不同点:
- 接口中必须都是抽象方法和常量,抽象类中可以定义普通方法
- 接口可以多继承,抽象类只能单继承
- 抽象类用于约定子类应该具备的功能;接口用于约定实现类应该具备的功能
day04
this关键字
① 只能在构造方法中使用this调用其他的构造方法,不能在成员方法中使用它来调用构造方法
② 在构造方法中,使用this调用构造方法的语句必须是该方法的第一条执行语句,且只能出现一次
③ 不能在一个类的两个构造方法中使用this互相调用
this三种用法
①调用成员变量(了解即可)
②调用其他成员方法(非static)
③调用本类其他构造方法
super关键字
① 通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。
② 如果子类构造方法没有显式调用父类构造方法时,那么jvm会默认调用父类的无参构造方法super()
super三种用法
①访问父类非私有字段(了解)
②访问父类非私有方法
③访问父类构造方法
static关键字
① 在一个静态方法中只能访问用static修饰的成员,原因在于没有被static修饰的成员需要先创建对象才能访问,而静态方法在被调用时可以不创建任何对象
②在类中,被static修饰的成员变量和成员方法不属于对象,而是属于类的,被类的所有实例或对象所共享
③ 当类被加载时,静态代码块会执行,并且在类第一次使用时才会被加载,只会加载一次
静态方法特性
【1】静态方法中可以访问静态变量和类的其他静态方法,但不能访问非静态的
(类的字节码文件首先被加载到方法区/静态区,并为静态变量初始化并分配内存空间,而类的实例对象可能还没有创建和初始化,内存当中是【先】有的静态内容,【后】有的非静态内容。)
【2】实例方法中可以访问静态成员(静态变量和静态方法)
(静态成员在类加载的时候就已经被加载并分配好内存空间了,在实例对象创建前就已经被加载了)
【3】静态方法当中不能用this
(this代表当前对象,通过谁调用的方法,谁就是当前对象)
final关键字
① final修饰的类不能被继承
② final修饰的方法不能被子类重写
③ final修饰的变量(成员变量和局部变量)是常量,只能赋值一次
jvm扫描字节码文件
1》读取字段,计算未来new对象时所需要的堆空间
2》读取方法,标记方法签名,方便未来调用
3》扫描静态变量并分配内存空间
内部类
-
静态内部类:使用static修饰的内部类,那么访问内部类直接使用外部类名来访问
-
实例(成员)内部类:没有使用static修饰的内部类,访问内部类使用外部类的对象来访问
-
局部(方法)内部类:定义在方法中的内部类,一般不用
-
匿名内部类:特殊的局部内部类,适合于仅使用一次的类
[1] 当一个类只使用一次时,可以声明成匿名内部类
[2] 匿名内部类必须有实现存在
枚举概述
- 专门用于定义可罗列的常量值
- 是一种特殊的类,其中的枚举项(常量值)都是public static final 类名 的类型
- 可以声明变量,且变量的取值必须是枚举中定义的公共静态常量值
day05
工具类实现方式
- 静态方法(私有化构造器,方法使用static修饰)
- 单例
单例模式
目的:保证在整个应用中某一个类有且只有一个实例(一个类在堆内存只存在一个对象)
饿汉模式(线程安全)
- 创建该类的一个对象(用private static final修饰)
- 阻止外界实例化
- 提供单例的公共访问方法
(确定这个单例未来一定会用,用饿汉模式:立即创建)
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
//把工具方法附着到单例。
//注:该方法不能是static修饰的,因为它是归单例所有
public String array2String(int[] arr){
String str = "[";
...............
}
}
懒汉模式(线程不安全)
- 创建该类的一个对象并赋为空(用private static修饰)
- 阻止外界实例化
- 提供单例的公共访问方法
(不确定这个单例用不用,用懒汉模式:延迟创建)
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
//把工具方法附着到单例。
//注:该方法不能是static修饰的,因为它是归单例所有
public String array2String(int[] arr){
String str = "[";
...............
}
}
包装类概述
把一个基本数据类型封装到一个包装类中,类中向外提供了方法来操作这个基本数据类型
为什么需要包装类
在Java中,很多类的方法都需要接收引用类型的对象,此时无法将一个基本数据类型的值传入,通过包装类可以将基本数据类型的值包装为引用数据类型的对象,对象可以做更多的功能。
Integer常用方法
valueOf(int/String):将int/String
转换为Integer
toString():返回一个表示该 Integer
值的 String
intValue():将Integer
转换为int
parseInt(String):将String
转换为int
*注:*Integer是int和String之间进行转换的桥梁
自动装箱
基本数据类型 ----直接赋值----> 包装类对象
自动拆箱
包装类对象 ---- 直接赋值 ----> 基本数据类型
包装类缓存设计
例:Integer包装类
Integer 在初始化的时候已经初始化好了256个Integer对象,并放到一个数组缓冲区中,如果范围在-128 到127,Integer 对象是在IntegerCache . cache 产生,会复用已有对象,直接从缓存中取, 这个区间内的 Integer 值可以直接使用==进行判断,如果超过了缓存的范围,都会在堆上产生新的对象,并不会复用已有对象,而是直接new一个新对象,所以超出范围再进行比较时结果为false,推荐使用 equals 方法进行判断。
BigDecimal
- 只要使用BigDecimal,一定使用含字符串的构造 。
//常用方法
//加操作
BigDecimal r1 = bigDecimal.add(bigDecimal2);
//减操作
BigDecimal r1 = bigDecimal.subtract(bigDecimal2);
//乘操作
BigDecimal r2 = bigDecimal.multiply(bigDecimal2).setScale(2, RoundingMode.HALF_UP);//保留2位小数
//除操作
BigDecimal r3 = bigDecimal.divide(bigDecimal2, 3, RoundingMode.HALF_UP);//保留3位小数
day06
不可变字符串String
String在内存中是以字符数组的形式存在,String对字符数组进行了封装,并提供了只读的形式来操作这个字符数组。
String----不可变字符串:当String对象创建完毕之后,它们的值在创建后就不能更改,一旦改变就变成了一个新的对象,因不可变,所以可以共享。程序当中直接写上的双引号字符串,就分配在方法区的常量池中。
程序当中所有的双引号字符串,都是String类的对象。
字符串比较(== ,equals)
- ==用于比较两个字符串对象的内存地址是否相同
- equals方法用于比较两个字符串中的字符值是否相等
**注:**如果比较双方一个常量一个变量,推荐把常量字符串写在前面。
重写equals方法
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
// 如果对象地址一样,则认为相同
if (this == o)
return true;
// 如果参数为空,或者类型信息不一样,则认为不同
if (o == null || getClass() != o.getClass())
return false;
// 转换为当前类型
Person person = (Person) o;
// 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果
//Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题
return age == person.age && Objects.equals(name, person.name);
}
}
//这段代码充分考虑了对象为空、类型一致等问题
判断字符串非空
public static boolean hasLength(String str) {
return str != null && ! "".equals(str.trim());
}
可变字符串
StringBuilder/StringBuffer: 当对象创建完毕之后,内容可以发生改变并且对象仍保持不变
(StringBuilder 和 StringBuffer 的区别 )
*相同点:*都是字符串可变缓冲区,api提供了相同的增删改查操作
*不同点:*StringBuffer(jdk1.0)线程安全但效率低;StringBuilder(jdk1.5)线程不安全但效率高
***注:***通常情况下,如果创建一个内容可变的字符串对象,应优先考虑StringBuilder类
StringBuffer(StringBuilder)和String的区别
① String类定义的字符串是常量,一旦创建后,内容和长度都是无法改变的;StringBuffer(StringBuilder)表示字符容器,其内容和长度可以随时修改。
②String类重写了Object的equals()方法,而StringBuffer(StringBuilder)没有重写。
③String类可以用操作符+进行连接,而StringBuffer(StringBuilder)对象之间不能。
自动扩容机制
初始化默认容量是16的可变字符串,如果超过了默认容量,则会自动进行扩容,每次扩容规则是(当前最大容量 + 1)* 2;
day07
Date
表示特定的瞬间,精确到毫秒
Date()
:可以自动设置当前系统时间的毫秒时刻
Date(long date)
:指定long类型的构造参数,可以自定义毫秒时刻
getTime()
:把日期对象转换为对应的时间毫秒值
SimpleDateFormat
SimpleDateFormat(String pattern)
:用给定的模式(格式)来指定格式化或解析的标准
- 格式化(format):按照指定的格式,将Date类型格式化为String类型
- 解析(parse):按照指定的格式,将String类型解析为Date类型
Calendar
Calendar类用于完成日期和时间字段的操作,Calendar本身是一个抽象类,不可以被实例化,通过调用其静态方法getInstance获取对象(此对象是单例的)
Date与Calendar之间相互转换:
getTime()
方法会返回一个表示Calendar时间值的Date对象
setTime(Date date)
方法接收一个Date对象,返回一个Calendar对象
小贴士:
西方星期的开始为周日,中国为周一。
在Calendar类中,月份的表示是从0-11分别代表1-12月。
日期是有大小关系的,时间靠后,时间越大。
System
currentTimeMills()
:返回与1970年01月01日00:00点之间的毫秒差值
arraycopy(Object srcArr,int srcIndex,Object targetArr,int targetIndex,int length)
:将数组中指定的数据拷贝到另一个数组中
数组拷贝
public class ArrayUtils {
private ArrayUtils(){}
public static void arrayCopy(int[] srcArr,int srcIndex,int[] targetArr,int targetIndex,int length){
for(int i = 0;i < length;i++) {
targetArr[targetIndex + i] = srcArr[srcIndex + i];
}
}
}
数组(冒泡)排序
public class ArrayUtils {
private ArrayUtils(){}
public static void arraySort(int[] array){
int temp;
//外层循环用于控制轮数
for(int i = 0;i < array.length - 1;i++){
//内层循环用于控制每轮中需要两两比较的次数
for(int j = 0;j < array.length - 1 - i;j++) {
if(array[j] > array[j + 1]){
temp =array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
}
二分法查找
***注意:***前提数组元素是有序的
public class ArrayUtils {
private ArrayUtils(){}
public static int binarySearch(int[] array,int target){
int low = 0;
int high = array.length - 1;
int mid,val;
while(low <= high){
mid = (low + high) / 2;
val = array[mid];
if(target > val){
low = mid + 1;
}else if(target < val){
high = mid - 1;
}else {
return mid;
}
}
return -1;//没有找到
}
}
Arrays
该类提供了多种对数组进行操作的方法,且该类为不同数据类型的数组都重载了相同功能的方法。
toString(Object[] arr)
:打印数组元素的功能
copyOf(Object[] arr,int newLength)
:从0索引开始拷贝newLength个数到新数组的0索引中
sort(Object[] arr)
:对指定的无序数组进行升序排序
binarySearch(Object[] arr,Object key)
:查找指定的key在数组中的位置
asList(T... e)
:把一个Object数组转换为List集合,并且这个集合的长度是固定的,不能增也不能减
day08
数据结构
用于研究数据在计算机中的存储和组织方式,根据不同数据的组织方式,增删改查的效率会有不同,因此实际开发过程中,根据不同的业务场景选用不同的数据结构
性能分析
- (ArrayList)数组的数据结构在已知索引的情况下,查询和修改效率高;添加和删除元素效率低
- (LinkedList)链表的数据结构在添加和删除效率高,查询和修改效率低
- (stack)栈是操作受限的线性表,只允许在栈顶进行操作,先进后出
- (queue)队列是操作受限的线性表,只允许在头尾进行操作,先进先出
集合概述
集合是java中提供的一种容器,可以用来存储任意类型的对象(引用类型),并且长度可变。
- 数组的长度是固定的,集合的长度是可变的
- 数组存储的是同一类型的元素,集合存储的都是对象,而且对象的类型可以不一致
集合的分类
List集合存储特点
- 允许元素重复
- 有序
- 是一个带有索引的集合,通过索引可以精确的操作集合中的元素
单列集合(List)常用方法
add(Object e)
:将元素添加到列表的末尾
remove(int index)
:删除列表中指定索引位置的元素
set(int index,Object o)
:修改指定索引的元素
get(int index)
:获取指定索引对应的元素
size()
:返回当前集合中元素个数
isEmpty()
:判断当前集合是否为空
contains(E)
:判断集合中是否存在某个元素
clear()
:清空集合中的所有元素
iterator()
:返回一个迭代器,用于遍历集合中的所有元素
toArray()
:将集合元素转换为数组
集合中存储自定义对象
在集合中,存储自定义对象(引用类型)时,存储的都是对象的内存地址值
泛型概念
通常用于表示更广泛的类型,实际上就是类型参数,当一个类或者接口中的数据类型不确定时,就可以考虑使用泛型。泛型只能是***引用类型***,不能是基本类型
泛型通配符<? extends T >来接收返回的数据,此写法的泛型集合不能使用 add 方法,而 <? super T> 不能使用 get 方法,因此频繁往外读取内容的,适合用<? extends T >。经常往里插入的,适合用 <? super T> 。
泛型的好处
- 将运行时期的异常转移到了编译时期变成编译失败
- 避免了类型强转的麻烦
- 让一个类或接口可以接受多种数据类型
day09
单列集合的遍历
- 普通遍历
- 增强for循环遍历
- 迭代器遍历
集合并发修改异常
在遍历集合的过程中,不能向集合中添加或者删除元素,要想在遍历的过程中添加或删除元素,可以使用迭代器的remove()
方法进行删除操作
性能分析
- HashSet,底层采用哈希表实现,做等值查询效率高
- TreeSet,底层采用红黑树(平衡二叉树)实现,可以对集合中的元素进行自动排序,做范围查询效率高
hash表的工作原理
①如果想在hash表中存储一个元素,先要计算该元素的hashCode值,用该值散列出其存储位置,如果该位置没有元素,就添加到该位置,②如果该位置已存在元素,则通过equals方法比较元素的内容是否相同,相同则不再添加,不同就在单链表上的同一个位置最后连接上该元素,确保没有重复元素出现 。
单列集合(Set)常用方法
add(E)
:向集合中添加元素
remove(E)
:删除指定元素
size()
:获取集合的大小
isEmpty()
:判断集合是否为空
contains(E)
:集合中是否包含指定的元素
HashSet集合存储特点
- 不允许元素重复
- 无序(如果要保证有序,可以使用HashSet的子类LinkedHashSet)
存储自定义对象
- 当往Set集合中存储自定义对象时,需在自定义的对象中重写HashCode()和equals(),确保同一个对象的所有字段值都一样时不能再往集合中添加该对象。
- hashcode不同,散列出来的位置可能一样也可能不一样
- hashcode相同,散列出来的位置一定一样
day10
TreeSet集合存储特点
不允许元素重复,元素对象必须是相同的数据类型,元素输出默认使用升序进行排序,是底层数据结构导致的,内部采用的是红黑树算法(平衡二叉树)
存储自定义对象(面)
向TreeSet中添加元素,一定需要具备比较大小的能力,①可以实现Comparable(内部比较器),并实现其compareTo方法(自然排序),②也可以实现Comparator,并实现其compare方法(定制排序)(通常都是在创建对象的时候传入new Comparator()(外部比较器)匿名内部类),在方法体中实现比较策略。
Map集合存储特点
- 用于存储具有键值映射关系的元素
- 键是唯一的,值可以不唯一,可以通过键找到对应的值,键一定要重写hashCode和equals方法
- 无序(如果要保证有序,可以使用LinkedHashMap)
性能分析
- HashMap的 key 的底层数据结构是哈希表
- TreeMap底层数据结构是红黑树算法(存储自定义对象时和TreeSet相同)
双列集合(Map)常用方法
put(Object key,Object value)
:存储一个键值对到Map中
remove(Object key)
:从Map中删除指定key的键值对,并返回被删除key对应的value
put(Object key,Object value)
:存储相同的key,后存储的key会把前面存储的值覆盖掉
get(Object key)
:通过key获取对应的value
size()
:获取Map中键值对个数
isEmpty()
:判断当前Map中键值对个数是否为0
keySet()
:返回Map中所有key所组成的Set集合
values()
:返回Map中所有value所组成的Collection集合
entrySet()
:返回Map中所有键值对所组成的Set集合
containsKey(Object key)
:判断Map中是否包含指定的key
containsValue(Object value)
:判断Map中是否包含指定的value
Collections常用方法
addAll(Collection<T> c, T... elements)
:往集合中添加一些元素
shuffle(List<?> list)
:打乱集合的顺序
sort(List<T> list)
:将集合中的元素按照默认规则排序
sort(List<T> list,Comparator<? super T)
:将集合中的元素按照指定的规则进行排序
//自定义排序
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
//按照第一个单词首字母进行降序排序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(list);//[sba, nba, cba, aba]
}
}
Map 类集合 K / V 能不能存储 null 值的情况
day11
什么是异常
指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
异常中的return
- 存在return的try…catch…finally块,finally先执行,然后再执行return
- return总是最后执行的
异常分类
- 常见Error
StackOverflowError:当应用程序递归太深而发生堆栈溢出时,抛出该错误。比如死循环或者
没有出口的递归调用。
OutOfMemoryError:因为内存溢出或没有可用的内存提供给垃圾回收器时,Java 虚拟机无法分配一个对象,这时抛出该错误。比如 new 了非常庞大数量的对象而没释放。
- 常见Exception
RuntimeException:在程序运行过程中可处理,可不处理
常见的运行时异常:
InputMismatchException:输入不匹配异常
ArithmeticException:数学计算异常 e.g:除数为 0
IndexOutOfBoundsException:下标/索引越界异常
ArrayIndexOutOfBoundsException : 数组下标越界异常
StringIndexOutOfBoundsException : 字符串下标越界
NullPointerException : 空指针异常
ClassCastException : 强制类型转换异常
NumberFormatException : 数字格式转换异常,如把"abc"转换成数字Checked Exception:也称为编译时异常,指在编译期间检查程序可能存在不正常的情况,在程序运行过程中必须处理,否则编译不通过
常见的检查时异常:
ParseException : 格式(日期时间等)解析异常
ClassNotFoundException : class 没找到异常
FileNotFoundException : 文件未找到异常
IOException:IO 异常
SQLException : 数据库相关异常
UnsupportedEncodingException : 不支持的字符串编码异常
throw和throws的区别
- throw用于方法体内,并且主动抛出的是一个异常类对象,将这个异常对象传递到调用者处,并结束当前方法的执行,而***throws用在方法声明中***,用来声明异常,表示当前方法不处理异常,而是提醒该方法的调用者来处理异常,用来指明方法可能抛出的多个异常
什么是自定义异常类
在开发中根据自己业务的异常情况来定义异常类
如何定义自定义异常类
- 自定义一个编译期异常: 自定义类需要继承于
java.lang.Exception
。 - 自定义一个运行时期的异常类:自定义类需要继承于
java.lang.RuntimeException
。
异常总结
- 运行时异常被抛出可以不处理。即不捕获也不声明抛出
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
- 不能在 finally 块中使用 return , finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句
day12
什么是多线程
指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信,并且可以共享资源,我们把这种情况称之为线程调度。
实现线程的方式
- 继承Thread
- 创建一个自定义线程对象
- 通过对象调用start方法
- 实现Runnable接口
- 创建一个自定义线程对象
- 创建一个Thread对象,并把runnable对象作为参数传入
- 通过Thread对象调用start方法
继承Thread和实现Runnable之间的区别
- 适合多个线程去处理同一个共享资源,把线程同程序代码,数据有效的分离,很好的体现了面向对象的设计思想。
- 避免单继承带来的局限性
线程生命周期及状态转换
线程会在一下两种情况进行阻塞状态:
- 当线程A运行过程中,试图获取同步锁时,却被线程B获取,此时jvm把当前线程A存到对象的锁池中,A就进入阻塞状态
- 线程在运行的过程中发出I/O请求时,也会进入阻塞状态
操作线程的常用方法
join()
: 调用此方法的线程进入阻塞状态,等到此方法执行完毕后,再接着往下执行
sleep(long millis)
: 让正在执行的线程暂停一段时间,进入阻塞状态
setPriority(int x)
:用于设置优先级,从1到10,默认为5
setDaemon(true)
:用来设置当前线程为后台线程,若所有的前台线程都死亡,后台线程自动死亡
yield()
:该方法和sleep有点类似,都可以让当前正在运行的线程暂停,区别在于yield方法不会阻碍该线程,只是将该线程转换为就绪状态
线程同步概述
当多线程并发访问同一个资源对象时,可能出现线程不安全的问题,此时需要使用到同步
同步代码块
//继承方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
synchronized (MyAppleThread.class) {//如果是继承,同步监视器就用当前类所在的字节码,如果是实现就用this
if (num > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "吃了编号为" + num-- + "的苹果");
}
}
}
}
同步方法
//同步方法(实现方式)
public synchronized void eat(){
if(num > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "吃了编号为" + num-- + "的苹果");
}
}
同步锁
//定义一个Lock锁对象
private final Lock lock = new ReentrantLock();
//如果不加上else进行判断的话,会产生Maximum lock count exceeded的错误提示,因为当ticket减为0时,进行while循环,然后被加锁了,且没有释放锁,所用else就是用来解锁和结束循环的
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在发售第" + ticket-- + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}else{
lock.unlock();
break;
}
}
}
***注:***有共享资源时优先使用实现Runnable接口的方式。
线程安全为什么会效率低
会导致其他的线程阻塞,阻塞变为就绪需要时间,就绪被cpu选中运行也需要时间
线程池的好处
减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
day13
File类
getName()
:获取文件的名称
getPath()
:获取文件路径
getAbsolutePath()
:获取绝对路径
getParentFile()
:获取上级目录
exists()
:判断文件是否存在
isFile()
:是否是文件
isDirectory()
:是否是目录
createNewFile()
:创建一个新文件
delete()
:删除文件
mkdirs()
:创建当前目录和上级目录
listFiles()
:列出所有文件对象
编码和解码操作
- 编码:把字符串转换为byte数组 String—>byte[]
- 解码:把byte数组转换为字符串 byte[]—>String
字节与字符的关系
一个字符编码后可能是1个字节,2个字节,或3个字节
IO流注意要点
- 四大抽象流是不能创建对象的,一般的我们根据不同的需求创建他们不同的子类对象
- 不管是什么流,操作完毕都必须调用close方法,释放资源
缓冲流
也称为包装流(构造方法还可以再传入另外一个流,对此流进行包装,提供更强大的功能),这里存在一个设计模式------装饰者设计模式
转换流
InputStreamReader
类是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符
OutputStreamWriter
类是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节
序列化(写)
将一个Java对象转换为一个I/O流中字节序列的过程(writeObject)
(把内存中的Java对象写出到文件的过程)
反序列化(读)
将I/O流中的字节序列恢复为Java对象的过程(readObject)
(把硬盘中的文件信息读取成Java对象的过程)
//序列化与反序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("src/cn/kjcoder/classes/_20io/obj.txt"));
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("src/cn/kjcoder/classes/_20io/obj.txt"));
outputStream.writeObject(new User("hkj", "男", 12));
outputStream.writeObject(new User("yoona", "女", 22));
outputStream.writeObject(null);
Object obj = null;
while((obj = inputStream.readObject()) != null){
System.out.println(obj);
}
inputStream.close();
outputStream.close();
System.out.println("=====================================");
// 创建 学生对象
Student student = new Student("老王", "laow");
Student student2 = new Student("老张", "laoz");
Student student3 = new Student("老李", "laol");
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(student);
arrayList.add(student2);
arrayList.add(student3);
// 序列化操作
// serializ(arrayList);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
// 读取对象,强转为ArrayList类型
ArrayList<Student> list = (ArrayList<Student>)ois.readObject();
for (int i = 0; i < list.size(); i++ ){
Student s = list.get(i);
System.out.println(s.getName()+"--"+ s.getPwd());
}
private static void serializ(ArrayList<Student> arrayList) throws Exception {
// 创建 序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
// 写出对象
oos.writeObject(arrayList);
// 释放资源
oos.close();
}
***注:***需要序列化的对象必须实现Serializable接口,并且显示提供一个序列化版本号。如果对于一些字段不想序列化,在字段的数据类型前面加上transient关键字
day14
软件设计原则
- 可重⽤性
- 可拓展性
- 可维护性
采取的措施:
- ⾼内聚性:内聚,强调⼀个系统模块内的功能联系,每个模块只完成特定的功能,不同模块之间不会有功能的重叠,⾼内聚性可以提⾼软件的可重⽤性和可维护性
- 低耦合性:耦合,强调的是多个模块之间的关系,模块之间相互独⽴,修改某⼀个模块,不会影响到其他的模块,低耦合性提⾼了软件的可维护性
测试类别
黑盒测试:⿊盒测试也称功能测试,是通过测试来检测每个功能是否能正常使⽤,把程序看作⼀个不能打开的⿊盒⼦,在完全不考虑程序内部结构和内部特性的情况下,在程序的接⼝上进⾏测试,检查程序功能是否 按照需求规格说明书的规定正常使⽤
白盒测试:由开发⼈员来测试. ⼜称结构测试、透明盒测试、逻辑驱动测试或 基于代码 的测试。它是按照程序内部的结构测试程序,通过测试来检测产品内部动作是否按照设计规格说明书的规定正常执⾏。测试者必须检查程序的内部结构,从检查程序的逻辑着⼿,得出测试数据
Junit使用
- 定义一个测试类
- 包名命名为xxx.xxx.xxx.test
- 测试类名:被测试的类名+Test
- 定义测试方法
-
方法必须是public修饰
-
方法名:test+测试的方法名
-
返回值:void
-
参数列表:空参
- 给方法加上@Test
- 导入Junit依赖环境
@Before:在方法上添加该注解用于资源申请,所有测试方法在执行之前都会先自动执行该方法
@After:在方法上添加该注解用于资源释放,在所有测试方法执行完后,都会自动执行该方法
Properties
是Map的实现类, 继承于Hashtable
load(字节/字符流)
:通过输入流加载配置文件中的内容
store(字节/字符流,String comments)
:把集合中的临时数据持久化写入到硬盘中存储
getProperty(String key)
:通过属性名获取属性值
setProperty(String key, String value)
:保存一对属性
stringPropertyNames()
:所有键的名称的集合
xml概述
主要的作⽤是⽤来存储和传输数据,以及作为配置⽂件,在网络中传输
xml基础语法
- xml文档的后缀名 .xml
- xml第一行必须定义为文档声明
- xml文档中有且仅有一个根标签
- 属性值必须使用引号(单双都可)引起来
- 标签必须正确关闭
- xml标签名称区分大小写
Document获取
- XML 被程序读到内存中会形成⼀个 Document 对象,所有要解析 XML ,⾸先得先获取到 Document 对象
获取⽂档 Document 对象的步骤
-
声明⽂件 xml ⽂件
File file = new File(path)
; -
通过 DocumentBuilderFactory 的 newInstance ⽅法获取本身的对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
; -
通过 DocumentBuilderFactory 对象去获取 DocumentBuilder 对象
DocumentBuilder builder = factory.newDocumentBuilder()
; -
通过 DocumentBuilder 对象去解析获取 Document 对象
builder.parse(file)
;
一般我们都不使用原始的方式来解析xml,而是通过一些强大的解析工具:如Jsoup,Dom4j
通过Document获取xml节点常用方法
-
Document 对象:
Element getDocumentElement()
: 获取根节点 -
Element 对象:
NodeList getElementsByTagName(String name)
:通过标签名获取标签列表 -
Node 对象:
String getTextContent()
:获取节点的⽂本内容void setTextContent(String content)
:设置节点的⽂本内容
day15
什么是反射
在运行时期, 动态获取类中成员信息和动态调用对象的方法的过程,将类的各个组成部分封装为其他对象,这就是反射机制。作用就是在不知道对象的真实类型的情况下去调用对象真实存在的方法,还可以解耦
什么是字节码对象
当需要描述多个对象的共同特征和行为时,可以用Class 类来描述所有的类的特征,通过Class 这个类创建出的对象称为字节码对象。可以把类中的成员封装为三⼤类对象来进⾏管理,分别为***构造器对象***,⽅法对象,字段对象
Java代码经历的三个阶段
获取字节码对象的三种方式
-
通过 Class 类的 forName() ⽅法来获取字节码对象,将字节码文件加载进内存 常用
Class.forName(String classsName 类的全限定类名)
-
通过类型(基本/引用类型)的 class 字段来获取字节码对象
object(基本/引用数据类型).class
-
通过对象的 getClass() ⽅法来获取字节码对象
对象.getClass()
***注意:***不管用哪种方式获取到的字节码对象都是同一个,因为同一份字节码在程序运行的过程中,只会被加载一次
获取构造器对象
- 获取所有的构造器对象
getConstructors()
:获取所有的 public 修饰的构造器
getDeclaredConstructors()
:获取所有的构造器(忽略访问权限)
- 获取指定的构造器对象
getConstructor(Class... parameterTypes)
:获取公共的构造器
getDeclaredConstructor(Class... parameterTypes)
:获取指定(忽略访问权限)并且带参数的构造器
构造器对象.newInstance(有参或无参) 等价于 class类对象.newInstance(无参)
:通过构造器对象/类对象来创建真实对象
setAccessible(boolean flag)
:传递⼀个true,表示忽略访问权限修饰符的安全检查(暴力反射)
注意要点
- 只要看到配置文件中传⼊全限定类名,基本上都是通过反射机制来创建字节码对象
- 只要看到⽆指定构造器但是能创建对象,基本上都是要通过Class对象的 newInstance 去创建对象
获取方法对象
- 获取所有⽅法
getMethods()
:可以获取到所有的公共的⽅法,包括继承的
getDeclaredMethods()
:获取到本类中所有的⽅法,包括⾮public的,不包括继承的
- 获取指定的⽅法
getMethod(String name 方法名, Class<?>... parameterTypes 当前方法的参数列表的类型)
: 获取指定参数的 public 的⽅法
getDeclaredMethod(String name 方法名, Class<?>... parameterTypes 当前方法的参数列表的类型)
:获取指定参数的private ⽅法
method对象.invoke(Object obj 作用于哪个对象, Object... args 调⽤⽅法的实际参数)
如果方法是静态的,则obj参数为null即可
setAccessible(boolean flag)
传递⼀个true,表示忽略访问权限修饰符的安全检查(暴力反射)
注意要点
- ⽅法也是可以被访问私有修饰符修饰的,所以,如果要访问⾮ public 修饰的⽅法,需要在访问之前设置可访问
method.setAccessible(true)
- 如果调⽤的是静态⽅法,是不需要对象的,所以此时在invoke⽅法的第⼀个参数,对象直接传递⼀个null 即可
获取字段对象(了解)
- 获取所有字段
getFields()
:获取所有public修饰的字段
getDeclaredFields()
:获取所有的字段(不考虑权限修饰符)
- 获取单个字段
getField(String name)
:获取指定名称的 public修饰的成员变量
getDeclaredField(String name)
获取字段对象(不考虑权限修饰符)
set(Object obj,Object value)
get(Object obj)
setAccessible(boolean flag)
:传递⼀个true,表示忽略访问权限修饰符的安全检查(暴力反射)
注意要点
- 必须保证设置字段传⼊的对象和获取字段传⼊的对象是同⼀个
内省
内省机制的主要作用: 用于间接操作Javabean中的属性
(获取某个类的属性成员信息)
内省核心类Introspector
Introspector.getBeanInfo(Class<?> beanClass)
:获取字节码对象对应的JavaBean成员信息
Introspector.getBeanInfo(Class<?> beanClass, Class<?> stopClass(Object.class))
常用
BeanInfo接⼝-
getPropertyDescriptors()
:获取所有的属性描述器
PropertyDescriptor类
getName()
:获得属性的名称
getPropertyType()
:获得属性的类型
getReadMethod()
:获得⽤于读取属性值的⽅法对象(相当于获取到了getXxx⽅法的Method对象)
getWriteMethod()
:获得⽤于设置属性值的⽅法对象(相当于获取到了setXxx⽅法的Method对象)
public static Map<String,Object> javaBean2Map(Object bean) throws Exception {
HashMap<String,Object> map = new HashMap<>();
Class<?> aClass = bean.getClass();
BeanInfo beanInfo = Introspector.getBeanInfo(aClass, Object.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String key = propertyDescriptor.getName();
Method readMethod = propertyDescriptor.getReadMethod();
Object value = readMethod.invoke(bean);
map.put(key, value);
}
return map;
}
public static Object map2JavaBean(Map<String,Object> map,Class cla) throws Exception {
Object obj = cla.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(cla, Object.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String key = propertyDescriptor.getName();
Object value = map.get(key);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(obj, value);
}
return obj;
}
day16
函数式接口
- 接口中有且仅有一个抽象方法 (
@FunctionalInterface
)
public class FunctionInterfaces {
public static void main(String[] args) {
// 需求4:编写getString⽅法返回⻓度⼤于5的字符串的集合
//普通方式
List<String> list = Arrays.asList("yoona", "hkj", "yoonas", "coderkj", "lisaasaas");
List<String> stringList = getString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.trim().length() > 5;
}
});
stringList.stream().forEach(System.out::println);
//lambda
List<String> list1 = getString(list, s -> s.trim().length() > 5);
list1.stream().forEach(System.out::println);
}
private static List<String> getString(List<String> list, Predicate<String> predicate){
List<String> arrayList = new ArrayList<>();
for(String str : list){
if(predicate.test(str)){
arrayList.add(str);
}
}
return arrayList;
}
// 需求3:编写getStringRealLength⽅法返回字符串真实⻓度
/*public static void main(String[] args) {
//普通方式
int length = getStringRealLength(" hkj & yoona ", new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.trim().length();
}
});
System.out.println(length);
//lambda
int length1 = getStringRealLength(" hkj & yoona ", s -> s.trim().length());
System.out.println(length1);
}
private static int getStringRealLength(String str, Function<String,Integer> function){
return function.apply(str);
}*/
// 需求2:编写getCode⽅法返回指定位数的随机验证码字符串
/*public static void main(String[] args) {
//普通方式
String code = getCode(4, new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt(10);
}
});
System.out.println(code);
//lambda
String code1 = getCode(4, () -> new Random().nextInt(10));
System.out.println(code1);
}
private static String getCode(int number, Supplier<Integer> supplier){
StringBuilder sb = new StringBuilder();
for(int i = 0;i < number;i++){
sb.append(supplier.get());
}
return sb.toString();
}*/
// 需求1:编写shop⽅法输出消费多少元
/*public static void main(String[] args) {
//普通方式
shop(1000, new Consumer<Integer>() {
@Override
public void accept(Integer money) {
System.out.println("一共消费" + money + "元");
}
});
//lambda
shop(1000, (money) -> System.out.println("一共消费" + money + "元"));
}
private static void shop(int money, Consumer<Integer> consumer) {
consumer.accept(money);
}*/
}
lambda
- Lambda允许把函数作为⼀个⽅法的参数,或者把代码看成数据
拒绝性能浪费
public class PerformanceOfTheWasteaLambda {
public static void main(String[] args) {
/*代码存在问题:⽆论级别是否满⾜要求,作为log ⽅法的第⼆个参数,三个字符串⼀定会⾸先被拼接并传⼊⽅法内,然后才会进⾏级别判断。如果级别不符合要求,那么字符串的拼接操作就⽩做了,存在性能浪费。*/
/*解决措施:
定义⼀个函数式接⼝并对log⽅法进⾏改造
这样⼀来,只有当级别满⾜要求的时候,才会进⾏三个字符串的拼接;否则三个字符串将不会进⾏拼接。*/
String name = "张无忌";
String action = "夜店消费";
String money = "200000元";
//延迟拼接字符串
/*logLambdaMethod("info", new Supplier<String>() {
@Override
public String get() {
System.out.println("come in");
return name + action + money;
}
});*/
logLambdaMethod("info",() -> {
System.out.println("come in");
return name + action + money;
});
}
private static void logLambdaMethod(String level, Supplier<String> sup) {
if ("info".equals(level)) {
System.out.println("come in");
System.out.println(sup.get());
}
}
}
Stream
- Stream是操作处理数据的⼀套⼯具,封装了对数据的各种操作:⽐如查找排序(sorted)、过滤(filter)和映射(map)
- 在进行聚合操作时,只是改变了Stream流对象中的数据,并不会改变原始集合或数组中的源数据
创建流的几种方式
1)调用Collection集合的默认方法 集合变量名.stream()
并发 和 集合变量名.parallelStream()
并行
2)Arrays.stream(数组)
3)Stream.of(可变参数)
Stream.iterate()
了解
Stream.generate()
了解
Stream常用方法
筛选和切片
filter(Predicate<T> p)
:过滤
distinct()
:去重(根据流中数据的 hashCode和 equals去除重复元素)
limit(long n)
:限定保留n个数据
skip(long n)
:跳过n个数据
映射
map(Function<T, R> f)
:接收⼀个函数作为参数,该函数会被应⽤到流中的每个元素上,并将其映射成⼀个新的元素
flatMap(Function<T, Stream<R>> mapper)
:接收⼀个函数作为参数,将流中的每个值都换成另⼀个流,然后把所有流连接成⼀个流
排序
sorted() sorted(Comparable<T> com)
:⾃然排序使⽤Comparable的int compareTo(T o)⽅法
sorted(Comparator<T> com)
:定制排序使⽤Comparator的int compare(T o1, T o2)⽅法
查找匹配
allMatch(断言型接口)
:检查是否匹配所有元素
anyMatch(断言型接口)
:检查是否⾄少匹配⼀个元素
noneMatch(断言型接口)
:检查是否没有匹配的元素
findFirst()
:返回第⼀个元素(返回值为Optional isPresent() get())
findAny
:返回当前流中的任意元素(⼀般⽤于并⾏流)
统计
count()
:返回流中元素的总个数
max(Comparator<T>)
:返回流中最⼤值
min(Comparator<T>)
:返回流中最⼩值
汇总collect()
collect(Collectors.xxx)
:将流转换为其他形式
并行流
parallel()
:将普通流(串行流)转换为并行流
sequential()
:将并行流转换成普通流
parallelStream()
:将集合直接转换为并行流
day17
自定义注解
//@interface⽤来声明⼀个注解,其中的每⼀个⽅法实际上是声明了⼀个配置参数。
//⽅法的名称就是参数的名称,返回值类型就是参数的类型
//格式
public @interface 注解名称{
属性列表;
}
//1>属性列表的返回值类型有下列取值
* 基本数据类型
* String
* Class类型
* 枚举
* 注解
* 以上类型的数组
//2>定义了属性时,在使用时需要给属性赋值
* 如果定义属性时,使用default关键字给属性默认初始化值,则在使用注解时,可以不进行属性的赋值
* 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义即可
* 数组赋值时,值使用{}包裹,如果数组中只有一个值,则{}可以省略
元注解
-
用于描述注解的注解
-
@Target:用于描述注解能够作用的位置
- ElementType取值: TYPE:可以作用于类、接口、枚举上 METHOD:可以作用于方法上 FIELD:可以作用于成员变量上 CONSTRUCTOR:可以作用于构造方法上 PARAMETER:可以作用于参数上 LOCAL_VARIABLE:可以作用于局部变量上
-
@Retention:描述注解的⽣命周期,注解被保留的阶段
- RetentionPolicy.RUNTIME:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
- RetentionPolicy.CLASS:表示注解会被编译到字节码中,但是JVM不加载注解
- RetentionPolicy.SOURCE:表示注解只存在于源⽂件中,不会被编译到字节码中
-
@Documented:描述注解是否被抽取到api文档中
-
@Inherited:描述注解是否被子类继承
解析注解
// 需求:获取Student类上贴的注解VIP的属性的值
public static void getVIPAnno() throws ClassNotFoundException {
//获取类的字节码对象
Class<?> aClass = Class.forName("cn.kjcoder.classes._03anno.anno1.Student");
if(aClass.isAnnotationPresent(VIP.class)){
//通过字节码对象调用方法获取指定注解对象
VIP annotation = aClass.getAnnotation(VIP.class);
Long value = annotation.id();
System.out.println(value);//2
String[] hobby = annotation.hobby();
Arrays.stream(hobby).forEach(System.out::println);//java php
String value1 = annotation.value();
System.out.println(value1);//默认值
}
}
// 需求:获取Student类中方法上贴的注解VIP的属性的值
public static void getVIPAnno1() throws Exception {
//获取类的字节码对象
Class<?> aClass = Class.forName("cn.kjcoder.classes._03anno.anno1.Student");
//获取贴有注解 的方法 的方法对象
Method method = aClass.getDeclaredMethod("study");
if(method.isAnnotationPresent(VIP.class)){
//获取方法的指定的注解对象
VIP annotation = method.getAnnotation(VIP.class);
System.out.println(Arrays.toString(annotation.hobby()));//[money, girl]
long id = annotation.id();
System.out.println(id);//3
}
}
SQL分类
- DDL(Data Definition Language)数据定义语言
用来定义数据库对象:数据库,表,列等。关键字:create, drop,alter 等
- DML(Data Manipulation Language)数据操作语言
用来对数据库中表的数据进行增删改。关键字:insert, delete, update 等
- DQL(Data Query Language)数据查询语言
用来查询数据库中表的记录(数据)。关键字:select, where 等
- DCL(Data Control Language)数据控制语言(了解)
用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANT, REVOKE 等
数据库查询语法
- 语法:
//执行顺序 from 》 where 》 select 》 order by
select
字段列表
from
表名列表
where
条件列表
group by
分组字段
having
分组之后的条件
order by
排序
limit
分页限定
//查看 MySQL 内部设置的编码
show variables like 'character%';
//解决乱码问题,设置统一字符集编码
set names 字符集
//添加外键约束emp_depid_fk
constraint emp_depid_fk foreign key (dep_id) references 主表(id)
//创建从表时添加外键约束
foreign key (cid) references 主表(cid)
例:foreign key (rid) references tab_route(rid),
foreign key(uid) references tab_user(uid)
//删除外键约束 emp_depid_fk
alter table 从表 drop foreign key emp_depid_fk;
//使用 truncate 删除表中所有记录
truncate table 表名;
//备份 ssm 数据库中的数据到 d:\ssm.sql 脚本文件存储地址
mysqldump -uroot -padmin ssm > d:/ssm.sql
//还原 ssm数据库中的数据,还原的时候需要先登录 MySQL,并选中对应的数据库
use ssm;
source d:/ssm.sql;
疑:如果一个字段设置了非空与唯一约束,该字段与主键的区别
- 主键数在一个表中,只能有一个。不能出现多个主键。主键可以单列,也可以是多列
- 自增长只能用在主键上
表之间的关系
三大范式
day18
JDBC概述
JDBC是官方定义的一套操作所有关系型数据库的规则(接口),接⼝的实现由各个数据库⼚商来完成,提供数据库驱动jar包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类,是一套用于执行SQL语句的Java API
JDBC操作步骤
- 注册驱动
- 获取数据库连接对象 Connection
- 通过数据库连接对象获取执行sql的预编译语句对象 PreparedStatement
- 调用PreparedStatement中的方法执行sql
- 释放资源
//详解各个对象
DriverManager
: 驱动管理对象
功能:注册驱动,告诉程序该使用哪一个数据库驱动jar
Connection
: 数据库连接对象
功能:获取执行sql的对象createStatement()
prepareStatement(String sql)
管理实务:开启事务setAutoCommit(boolean autoCommit)
参数为false即开启事务
提交事务:commit()
回滚事务:rollback()
Statement
: 执行sql的对象
功能:执行sqlexecuteUpdate(String sql)
executeQuery(String sql)
ResultSet
: 结果集对象,封装查询结果
next()
:游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回truegetXxx(参数)
:获取数据
PreparedStatement(String sql)
: 执行sql的对象
功能:执行sqlsetXxx(参数1,参数2)
: 给占位符赋值executeUpdate()
executeQuery()
注意:后期都会使用`PreparedStatement来完成增删改查的所有操作
- 可以有效的防止 SQL 注入的问题,安全性更高
- 因为mysql数据库有预先编译的功能,提高了SQL 的执行效率,提供了更好的性能
- 提高了程序的可读性和可维护性
PreparedStatement的执行原理
public class JDBCUtil {
private JDBCUtil(){}
private static Properties p;
static{
try {
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties");
p = new Properties();
p.load(in);
Class.forName(p.getProperty("driverClassName"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
try {
return DriverManager.getConnection(p.getProperty("url"),p.getProperty("username"),p.getProperty("password"));
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void close(ResultSet rs, PreparedStatement pst,Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pst != null){
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(PreparedStatement pst,Connection conn){
close(null,pst,conn);
}
}
JDBC问题解答
在注册驱动时,为什么使用Class.forName,而不使用DriverManager.registerDriver来注册驱动?
静态代码块存放了加载驱动的代码,当类被加载进内存时,静态代码块会被执行,就相当于加载了两次注册驱动,显然没有必要,因此通过反射来创建注册驱动对象只会被创建一次
为什么要使用prepareStatement?
- 可以有效的防止 SQL 注入的问题,安全性更高
- 因为mysql数据库有预先编译的功能,提高了SQL 的执行效率,提供了更好的性能
- 提高了程序的可读性和可维护性,使用Statement需要对sql语句进行拼接不利于阅读且容易出错
在释放资源时,为什么要先关闭resultSet,再关闭prepareStatement,最后再关闭connection?
因为resultSet对象是通过prepareStatement预编译语句对象执行DQL语句返回的结果集,而prepareStatement对象又是connection对象创建的,这就存在一个先后顺序,如果关闭顺序不一致,可能会导致程序出现异常等情况.
day19
事务概述
一个包含多个步骤的业务操作,如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败
事务的属性
原⼦性(Atomicity)
:原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣⼀致性(Consistency)
: 保证数据的完整性. 事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态隔离性(Isolation)
:事务的隔离性是指⼀个事务的执⾏不能被其他事务⼲扰,即⼀个事务内部的操作及使⽤的数据对并发的其他事务是隔离的,并发执⾏的各个事务之间不能互相⼲扰持久性(Durability)
:持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的
rollback和commit作用
- 回滚事务 (提交事务)
- 释放Connnection逻辑单元上的锁
连接池
概述:存放数据库连接的容器,先申请好一定数量的Connection对象,存放到连接池中,存起来重复使用,当需要连接对象时直接从连接池中取
注意:使用连接池时close()方法并不是和数据库断开连接,而是把连接对象归还给连接池,还可以再次使用,并没有关闭连接
好处:节约资源,用户访问高效
public class DruidUtil {
private DruidUtil(){}
private static Properties ps = new Properties();
private static DataSource ds = null;
static{
try {
//1.加载配置文件
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties");
ps.load(in);
//2.获取数据库连接池对象,通过工厂来获取
ds = DruidDataSourceFactory.createDataSource(ps);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
//3.通过数据库连接池来获取连接
return ds.getConnection();
}
public static void close(ResultSet rs, PreparedStatement pst, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pst != null){
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(PreparedStatement pst, Connection conn){
close(null,pst,conn);
}
/**
* 获取连接池方法
*/
public static DataSource getDataSource(){
return ds;
}
}
day20
框架的好处
框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率