java编程基础(进阶级)
01Object类
1.概述
【1】java.lang.Object 是根类,是所有类的父类
【2】如果一个类没有指定特别的父类,则默认继承Object类
2.toString方法
【1】源码:返回该对象的字符串表示
【2】直接打印对象的名字,就是调用对象的toString方法
【3】重写
//在Person类中
@override
public String toString() {
return "Person{name = "+name+" ,age = "+age+"}";
}
【4】快捷键:Control+Enter 找到toString()
3.equal方法
【1】源码:比较的是两个对象的地址值
【2】重写
(1)存在问题:多态的弊端:无法使用子类特有的内容
(2)解决方法:使用向下转型把Object转成Person
【3】快捷键
Control+Enter
找到euqals() and hashCode(),选择java 7+版本
4.Objects工具类的equals方法
【1】优点:避免空指针异常
【2】格式:为静态(static)可以直接用类名调用:
Objects.euqals(obj1,obj2);
02时间和日期相关类
1.Date类
概述:类 Date 表示特定的时间精确到毫秒
2.毫秒的概念和作用
【1】毫秒:千分之一秒; 0.001秒
【2】例子:2088-08-08 09:55:33:333
【3】作用:对时间和日期进行计算
2088-01-01 到 2099-01-03 中间有几天?
可以把日期转换为毫秒,计算完后再把毫秒转换回去
把日期转换为毫秒:
时间原点(0毫秒):1970年1月1日00:00:00(英国格林威治)
计算当前日期到原点经历了多少毫秒
把毫秒转换为日期:
1 天 = 24 * 60 * 60 * 1000 = 86400000毫秒
TIPS:
中国属于东八区,会把时间增加8个小时
3.Date类的构造方法和成员方法
【1】空参数构造方法
//当前系统的日期和时间
Date date = new Date();
【2】毫秒值转换成Date日期
//传递毫秒值,把毫秒值转换成Date日期
Date(long date);
Date date = new Date(0L);
【3】成员方法:getTime()获取
//从1970年1月1日00:00:00开始,日期转换为毫秒值
//相当于System.currentTimeMillis()
long getTime();
【4】成员方法:转换
//根据本地格式转换日期对象
String toLocaleString();
d.toLocaleString()
4.DateFormat类 & SimpleDateFormat类
【1】概述:在java.text.DateFormat中,是日期/时间格式化子类的抽象类
【2】作用:格式化(日期->文本),解析(文本->日期)
【3】成员方法:
//按指定的模式,把Date日期,格式化为符合模式的字符串
String format(Date date);
//把符合模式的字符串,解析为Date日期
Date parse(String source);
【4】DateFormat类是抽象类,无法直接创建对象使用,可以使用SimpleDateFormat的子类
【5】SimpleDateFormat的构造方法
//用给定的模式和默认语言环境的日期格式符号构造
//参数:Pattern:传递指定的模式
//模式:区分大小写,写对应的模式,会把对应的模式替换为时间和日期
//ex. "yyyy-MM-dd HH:mm:ss"
// "yyyy年MM月dd日 HH时mm分ss秒"
SimpleDateFormat(String Pattern);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
【6】模式:
标识字母(区分大小写) | 含义 |
---|---|
y | 年 |
M | 月(0-11) |
d | 日 |
H | 小时 |
m | 分钟 |
s | 秒 |
TIPS:
模式中的字母不能更改,连接模式的符号可以改变
【7】format方法
使用步骤:
(1)创建SimpleDateFormat对象,构造方法中传递指定的模式
(2)调用SimpleDateFormat中的方法format
按照构造方法中的模式,把Date日期格式转化成符合格式的字符串
【8】parse方法
使用步骤:
(1)创建SimpleDateFormat对象,构造方法中传递指定的模式
(2)调用SimpleDateFormat中的方法parse
把符合构造方法中模式的字符串,解析为Date日期
TIPS:
【1】parse方法中声明了一个异常叫做ParseException异常
【2】如果字符串和构造方法中的模式不一样,那么此异常发生
【3】调用一个抛出异常的方法,就必须处理这个异常
【4】处理方法1:throws继续声明抛出这个异常
【5】处理方法2:try…catch自己处理这个异常
5.Calendar类
【1】概述:在util中,是一个抽象类,提供了很多操作日历字段的方法
【2】因为是抽象类,无法直接创建对象使用
【3】使用方法:里面有一个静态方法getInstance(),该方法返回了Calendar类的子类对象
【4】获取对象的方式
// 使用默认时区和语言环境获得一个日历(对象)
static Calendar getInstance();
Calendar.getInstance();
【5】
//field:日历类的字段 可以使用Calendar类的静态成员变量获取
//public static final int YEAR = 1; 年
//public static final int MONTH = 2; 月
//public static final int DATE = 5; 月中某一天
//public static final int DAY_OF_MONTH = 5; 月中某一天
//public static final int HOUR = 10; 时
//public static final int MINUTE = 12; 分
//public static final int SECOND = 13; 秒
【6】成员方法1:获取 get
// 返回给定日历字段的值
// 参数:传递指定日历的字段
// 返回值:日历字段代表具体的值
public int get(int field)
【7】成员方法2:设置 set
// 将给定的日历字段设置为给定值
// 参数:field日历的字段,value设置的具体的值
public void set(int field, int value
//可以用set的重载方法同时设置年月日
c.set(8888,8,8)
【8】成员方法3:添加 add
// 根据日历的规则,为给定的日历字段添加或减去指定的时间量
public abstract void add(int field, int amount)
【9】成员方法4:getTime
// 返回一个表示此Calendar时间值的Date对象
// 日历对象转换为日期对象
public Date getTime()
03System类
1.概述
在lang包中,不用导入
2.返回当前时间currentTimeMillis
//返回以毫秒为单位的当前时间
public static long currentTimeMillis()
System.currentTimeMillis();
3.数组复制arraycopy
// 将数组中指定的数据拷贝到另一个数组中
// 参数: src:源数组
// srcPos:源数组的起始位置
// dest:目标数组
// destPos:目标数据中的起始位置
// length:要复制数组元素的数量
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
System.arraycopy( ...... );
04StringBuilder类
1.String类回顾
【1】字符串是常量,创建后值不能改变(底层是一个被final修饰的数组) (private final byte[] value;)
private final byte[] value;
【2】进行字符串的相加,内存就会有多个字符串,占用空间多,效率低下
String s = "a" + "b" + "c";
//"a","b","c"三个字符串
//"a"+"b" "ab" 一个字符串
//"abc" 一个字符串
//总共五个字符串
2.StringBuilder类概述&原理
【1】lang包中
【2】字符串缓冲区,可以提高字符串的操作效率(可以看作长度可变的字符串)
【3】底层也是一个数组,但没有被final修饰,可以改变长度
byte[] value = new byte[16];
【4】在内存中始终是一个数组,占用空间少,效率高
【5】如果超出容量,会自动扩容
3.构造方法
【1】空参数构造方法
// 构造一个空的StringBuilder容器
public StringBuilder()
StringBuilder sb = new StringBuilder();
【2】带字符串的构造方法
// 构造一个StringBuilder容器,并将字符串添加进去
public StringBuilder(String str)
StringBuilder sb2 = new StringBuilder("abc");
4.成员方法
【1】append 添加
// 添加任意类型数据的字符串形式,并返回当前对象自身
// append方法返回的是this(调用方法的对象)
// 使用append方法无需接收返回值
public StringBuilder append(...)
sb.append("abc");
//链式编程:方法的返回值是一个对象,可以根据对象继续调用方法
sb.append("1").append("abc").append("8.8");
【2】toString
String->StringBuilder:带参数的构造方法
//将当前StringBuilder对象转换为String对象
//StringBuilder->String
public String toString()
sb.toStirng();
【3】reverse 反转
//将当前StringBuilder对象反转
public StringBuilder reverse(...)
sb.reverse();
05包装类
1.概念
基本数据类型->包装成引用数据类型(都在lang包中)
2.装箱
【1】装箱:基本数据类型->包装类
【2】构造方法:
(1)
//构造一个新分配的Integer对象,它表示指定的int值
Integer(int value)
(2)
//构造一个新分配的Integer对象,它表示String参数指示的int值
//必须是基本类型字符串 (不能是"A")NumberFormatException
Integer(String s)
【3】静态方法:
(1)
//返回一个表示指定int值的Integer实例
//方法上有横线说明方法过时了
static Integer valueOf(int i)
(2)
//返回保存表示指定String值的Integer对象
static Integer valueOf(String s)
3.拆箱
【1】装箱:包装类->基本数据类型
【2】成员方法:
//以int类型返回该Integer的值
int intvalue()
4.自动装箱与自动拆箱
【1】基本数据类型和包装类之间可自动转换
【2】自动装箱:直接把int类型的整数赋值给包装类
Integer in = 1;
//相当于 Integer in = new Integer(1);
【3】自动拆箱:in是包装类无法直接参加运算,但可以自动转换为基本类型的数据,再参与计算
in = in + 2;
//相当于 in.intvalue() + 2 = 3;
// in = new Integer(3);
【4】ArraryList
自动装箱:list.add(1);
自动拆箱:list.get(0);
5.基本类型->字符串
【1】方法1:最简单【重点】的方式:基本类型+""
直接与””相连接即可;如:34+""
【2】 方法2:使用包装类中的静态方法
//返回一个表示指定整数的String对象
static String toString(int i)
String s2 = Integer.toString(100);
【3】方法3:使用String类中的静态方法
//返回int参数的字符串表示形式
static String valueOf(int i)
String s3 = String.valueOf(100);
6.字符串->基本类型
【1】除了Character类之外,其他所有包装类都具有parseXxx静态方法:
【2】
//将字符串参数转换为对应的byte基本类型
public static byte parseByte(String s)
//将字符串参数转换为对应的short基本类型
public static short parseShort(String s)
//将字符串参数转换为对应的int基本类型
public static int parseInt(String s)
//将字符串参数转换为对应的long基本类型
public static long parseLong(String s)
//将字符串参数转换为对应的float基本类型
public static float parseFloat(String s)
//将字符串参数转换为对应的double基本类型
public static double parseDouble(String s)
//将字符串参数转换为对应的boolean基本类型
public static boolean parseBoolean(String s)
06Collection集合
1.概述
【1】Collection< E >类在util包中(根接口,单列集合的最顶层,里面定义了所有单列集合共性的方法)
【2】ArrayList< E >是一个集合
【3】集合是一种容器可以存放多个数据
【4】和数组的区别:
(1)数组长度固定,集合可以改变长度
(2) 数组存储同一类型的数据,可以存储基本数据类型或对象
集合存储的只能是对象
2.集合框架
【1】目标:
(1)会用集合存储数据
(2)会遍历集合,取出数据使用
(3)掌握每种集合的特性
【2】框架:
【3】学习方式:
(1)学习顶层:学习顶层接口/抽象类的共性方法,所有子类都能使用
(2)使用底层:顶层(图中有错)不是接口就是抽象类,无法创建对象是用,需要使用底层的子类创建对象使用
3.集合的共性方法(7种)
【1】创建集合
//可以使用多态
//重写了toString方法
Collection<String> coll= new ArrayList<>();
【2】方法1:增加add
//把给定的对象添加到当前集合中
//返回值是一个boolean值,一般都返回true,所以可以不用接收
public boolean add(E e)
【3】方法2:清空clear
//清空集合中所有的元素
//集合还在,里面没有数据
public void clear()
【4】方法3:删除remove
//把给定的对象在当前集合中删除
//参数:元素名称
//返回值:存在删除元素,返回true,否则返回false
public boolean remove(E e)
【5】方法4:是否包含contain
//判断当前集合中是否包含给定的对象
//参数:元素名称
//返回值:若包含,返回true,否则返回false
public boolean contains(E e)
【6】方法5:判断是否为空isEmpty
//判断当前集合是否为空
//返回值:若集合为空,返回true,否则返回false
public boolean isEmpty()
【7】方法6:元素个数size
//返回集合中元素的个数
public int size()
【8】方法7:转换成数组toArray
//把集合中的元素,存储到数组中
public Object[] toArray()
07Iterator接口(迭代器)
1.位置
在java.util包中
2.含义
Collection集合通用的取出元素的方式,去处元素前需要判断集合内有没有元素,有就取出,再继续判断,还有就继续取出,一直把所有元素取出(遍历)
3.获取
【1】Iterator(迭代器)是一个接口,需要使用它的实现类对象
【2】获取实现类方法比较特殊:Collection接口中有一个方法叫做Iterator,这个方法返回一个Iterator实现类对象
4.常用方法
【1】判断hasNext
//判断有没有下一个元素
//返回值:有就返回true,否则返回false
public boolean hasNext()
【2】取出/返回Next
//返回迭代的下一个元素。
public E next()
5.使用步骤【重点】
【初始化】
Collection<String> coll = new ArrayList<>();
【1】使用Collection中的方法Iterator()获取Iterator实现类对象,使用Iterator接口来接收它(多态)
//多态
Iterator<String> it = coll.iterator();
【2】使用Iterator接口中的方法hasNext判断还有没有下个元素
it.hasNext();
【3】使用Iterator接口中的方法Next取出集合中的下一个元素
String s = it.Next();
TIPS:
Iterator< E >接口也有泛型,Iterator的泛型跟着集合走,集合是什么,Iterator就是什么
6.使用循环优化(2)(3)步
【1】不知道有几个元素:使用while循环
while (it.hasNext()) {
String e = it.Next();
}
【2】for循环
for(Iterator<String> it2 = coll.iterator();it2.hasNext();){
String e = it.Next();
}
7.使用原理
8.增强for循环(for each循环)
【1】概述:底层是个Iterator,使用for循环的格式,简化了Iterator的书写
【2】public interface Iterable< T >
实现这个接口允许对象成为“foreach”语句的目标
【3】Collection< E > extends Iterable< E >
所有单列集合都可以使用增强for循环
【4】作用:用来遍历集合和数组
【5】格式
for(集合/数组的数据类型 变量名:集合名/数组名){
sout(变量名);
}
int[] arr = {1,2,3,4,5};
for (int i:arr){
sout(i);
}
08泛型
1.概述
【1】是一种未知的数据类型,当不知道用什么数据类型的时候,可以使用泛型
【2】也可以看作是一个变量,用来接收数据类型
2.种类
【1】E e:Element 元素
【2】T t:Type 类型
3.例子
ArrayList类,创建时不知道用什么类型,所以用了泛型
4.结构图
创建集合对象时会确定泛型的类型
5.泛型的好处
【1】创建集合对象,使用泛型
- 好处:默认的类型就是Object类型,可以存储任意类型的数据
- 弊端:不安全,会引发异常
Object要向下转型是,会有ClassCastException异常
【2】创建集合对象,使用泛型 - 好处1:避免了类型转换的麻烦
- 好处2:把运行期异常提升到了编译期
- 弊端:泛型是什么数据类型,就只能存储什么数据类型
6.泛型的定义和使用(类)
【1】定义格式
public class GenericClass<E> {
private E name;
public E getname() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
【2】使用
//不写泛型默认为Object类型
GenericClass gc = new GenericClass();
Object obj = gc.getName();
//使用Integer泛型
GenericClass<Integer> gc2 = new GenericClass();
Integer num = gc2.getName();
7.含有泛型的方法
【1】定义格式
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)) {
方法体;
}
//含有泛型的方法
public <M> void method02(M m) {
sout(m);
}
//含有泛型的静态方法
public static <S> void method02(S s) {
sout(s);
}
【2】含有泛型的方法,在调用方法时确定泛型的数据类型
传递什么类型的参数,泛型就是什么类型
【3】使用:
GenericMethod gm = new GenericMethod();
gm.method01(10);
gm.method01("abc");
GenericMethod.method02(10);
GenericMethod.method02("abc");
8.含有泛型的接口
【1】定义格式
public interface GenericInterface<I> {
public abstract void method(I i);
}
【2】第一种使用方式:定义接口的实现类并且指定泛型
public class GenericInterfaceImpl1 implements GenericInterface<String>{
@override
public abstract void method(String s){
sout(s);
}
}
GenericInterfaceImpl1 gi1 = new GenericInterfaceImpl1();
gi1.method("abc");
【3】第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,相当于定义了一个含有泛型的类
public class GenericInterfaceImpl2 implements GenericInterface<I>{
@override
public abstract void method(I i){
sout(i);
}
}
GenericInterfaceImpl2<Integer> gi2 = new GenericInterfaceImpl2();
gi2.method(10);
9.泛型通配符<?>
【1】?代表任意的数据类型
【2】使用方式:不能创建对象使用,只能作为方法的参数使用
【3】例子
定义一个方法,能遍历所有类型的ArrayList集合
//我们不知道ArrayList集合使用什么数据类型,所有用通配符?
//泛型没有继承概念:不能写Object
public static void printArray(ArraryList<?> list){
Iterator<?> it = list.Iterator();
while (it.hasNext()) {
//it.next()方法取出Object类型,可以接收任意的数据类型
Obj o = it.next();
sout(o);
}
}
【4】泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
【5】泛型的下限限定:?super E 代表使用的泛型只能是E类型的父类/本身
public static void main(String[] args) {
//继承关系: Integer extends Number extends Object
// String extends Object
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
09数据结构
1.栈:先进后出
【1】存储元素到集合:入栈,压栈
【2】取出集合中的元素:出栈,弹栈
2.队列:先进先出
3.数组:查询快,增删慢
【1】查询快:数组的地址是连续的,通过数组的首地址可以找到数组,通过数组的索引可以快速查找某个元素
【2】增删慢:数组的长度是固定的,想要增加/删除一个元素,必须创建一个新数组,并且复制源数组的数据
4.链表:查询慢,增删快
【1】查询慢:链表中地址不是连续的,每次查询元素,都必须从头开始查询
【2】增删快:链表结构,增加/删除一个元素,对链表整体的没有影响,所以增删快
【3】链表中的每一个元素也称之为一个节点,一个节点包含了一个数据源(存储数据)和两个指针域(存储地址)
【4】单向链表:链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序可能不一致)
【5】双向链表:链表中有两条链子,有一条链子专门记录元素的顺序,有序的集合
5.红黑树
【1】特点:趋近于平衡树,查询速度非常的快,查询叶子节点最大次数和最小次数不能超过二倍
【2】约束:
(1)节点可以是红色或黑色的
(2)根节点是黑色的
(3)叶子节点(空节点)是黑色的
(4)每个红色的节点的子节点都是黑色的
(5)任何一个节点到其每一个叶子节点的所有路径上黑色节点的数量是相同的
10List接口
1.list接口
【1】位置:util包中
【2】list接口:继承Collection接口
【3】特点:
(1)有序的集合,存储元素和取出元素的顺序是一致的
(2)有索引,包含了一些带索引的方法
(3)允许存储重复的元素
【4】常用方法1:增加add
// 将指定元素添加到该集合的指定位置中
public void add(int index, E element)
【5】常用方法2:获取get
// 返回集合中指定位置的元素
public E get(int index)
【6】常用方法3:删除remove
// 移除列表中指定位置的元素,并返回它
public E remove(int index)
【7】常用方法4:替换set
// 用指定元素替换集合中指定位置的元素,返回值为更新前的元素
public E set(int index, E element)
【8】遍历方式
(1)普通for循环+get
(2)迭代器
(3)增强for
TIPS:
注意索引不要越界,会引发索引越界异常IndexOutOfBoundsException
2.实现类1:ArrayList集合
【1】已学习过
【2】List接口的大小可变数组的实现(底层是数组结构)
【3】此实现不是同步的(多线程)(效率高)
3.实现类2:LinkedList集合
【1】List接口的一个链表实现
【2】此实现不是同步的(多线程)(效率高)
【3】特点:
(1)底层是一个链表结构(查询慢,增删快)
(2)里面包含了大量操作首尾元素的方法
TIPS:
使用LinkedList集合特有的方法,不能使用多态
【4】常用方法
(1)添加 addFirst&addLast&push&add
// add
linked.add("a");
// addFirst将指定元素插入此列表的开头
public void addFirst(E e)
// addLast将指定元素插入此列表的结尾
public void addLast(E e)
// 等效于addFirst
public void push(E e)
(2)获取getFirst&getLast
// getFirst返回此列表的第一个元素
public E getFirst()
// getLast返回此列表的最后一个元素
public E getLast()
TIPS:
集合中没有元素,在获取时会出现NoSuchElementException
添加isEmpty()进行判断
(3)移除removeFirst&removeLast&pop
// removeFirst移除并返回此列表的第一个元素
public E removeFirst()
// removeLast移除并返回此列表的最后一个元素
public E removeLast()
// pop 等效于removeFirst
public E pop()
4.实现类3:Vector集合
【1】可以实现可增长的对象数组
【2】同步的(单线程)(效率慢)(被ArrayList取代)
【3】添加addElement(E obj)添加到末尾,大小增加1
【4】遍历Elements(Enumeration接口)
11Set接口
1.Set接口
【1】继承了Collection接口
【2】util包中
【3】特点
(1)不允许存储重复的元素
(2)没有带索引的方法,也不能使用普通的for循环遍历
2.实现类1:HashSet集合
【1】特点
(1)哈希表结构(速度快)
(2)没有顺序,无序集合
【2】此实现不是同步的
【3】添加:add方法
【4】遍历:迭代器 / 增强for
3.哈希值
【1】十进制的整数,由系统直接给出(就是对象的地址值,是一个逻辑地址,是模拟出来的地址,不是数据实际存储的物理地址)
【2】Object中有一个方法可以获取哈希值 hashCode()
//源码:
//native代表方法调用的是本地操作系统的方法
public native int hashCode();
//toString源码
//哈希值的十六进制表现形式
return getClass().getNanme() + "@" + Integer.toHexString(hashCode());
【3】String类的哈希值
String类重写了hashCode方法 96354
4.哈希表
【1】
- jdk1.8之前:数组+链表
- jad1.8之后:数组+链表 / 数组+红黑树(链表的长度超过8)(提高查询速度)
【2】特点速度快
【3】数组:把元素进行分组(相同哈希值的元素是一组)
【4】链表/红黑树:把相同哈希值的元素链接到一起
【5】哈希冲突:两个元素不同,但哈希值相同(在同一组)
【6】如果链表长度超过八位,那么就会把链表转化为红黑树
5.存储元素不重复的原理
【1】set集合在调用add方法时,add方法会调用hasCode方法和equal方法,判断元素是否重复
【2】前提:存储的元素必须重写hashCode方法和equal方法
6.HashSet存储自定义类型的元素
【1】set集合保证元素唯一:重写hashCode和equals方法
【2】步骤
Control+Enter选择equals()和hashCode()
7.实现类2:LinkedHashSet集合
【1】在util包中
【2】继承了HashSet集合
【3】特点:
(1)底层是一个哈希表(数组+链表/红黑树)+链表
(2)多了一条链表用来记录元素的存储顺序,保证元素有序
番外01:可变参数
1.使用前提
当方法的参数列表数据类型已经确定,但参数的个数不确定,就可以使用
2.使用格式
定义方法时使用
修饰符 返回值类型 方法名(数据类型...变量名){}
3.原理
底层是一个数组,根据传递参数个数不同,会创建不同长度的数组来存储这些参数,可以是0个(不传递),1个,2个 …
4.例子
//计算(0-n)个参数的和
public static int add(int...arr){
int sum = 0;
for (int i:arr){
sum += i;
}
return sum;
}
//使用
//add();创建长度为0的数组,new int[0]
//add(10);创建长度为1的数组,存储除传递来的参数new int[]{10};
//add(10,20);创建长度为2的数组,存储除传递来的参数new int[]{10,20};
TIPS:
【1】一个方法的参数列表,只能有一个可变参数
【2】如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
5.可变参数的特殊(终极)写法
public static void method(Object...obj){}
12Collections集合工具类
1.学过:shuffle方法
//打乱顺序:打乱集合顺序
public static void shuffle(List<?> List)
Collections.shuffle(list);
2. addAll方法
//往集合中添加一些元素
public static <T> boolean addAll(Collection<T> c,T...elements)
Collections.addAll(list,"a","b","c","d");
3. sort方法1
//将集合中的元素按照默认规则排序(升序)
public static <T> void sort(List<T> List)
Collections.sort(list);
TIPS:
【1】使用前提:被排序的集合里边存储的元素必须实现Comparable,重写接口中的方法compareTo(定义排序的规则)
【2】例子:Person
@override
public int compareTo(Person o){
//按年龄升序排序
return this.getAge() - o.getAge();
//按年龄升序降序
return this.getAge() - o.getAge();
}
【3】Comparable接口排序规则
(自己)this - 参数:升序
3. sort方法2
//将集合中的元素按照默认规则排序(升序)
public static <T> void sort(List<T> List, Comparator <? super T>)
//匿名内部类
Collections.sort(list,new Comparator<Integer>() {
@override
public int compare(Integer o1, Integer o2) {
return o1 - o2; //升序
return o2 - o1; //降序
}
});
Comparator和Comparable的区别
- Comparable:自己(this)和别人(参数)比较,需要实现Comparable接口,重写比较的规则comparaTo方法
- Comparator:相当于找一个第三方的裁判,比较两个
13Map集合
1.概述
【1】接口Map<K,V> 位置:util包中
【2】特点:
(1)是双列集合:一个元素包含两个值,一个key,一个value
(2)Map集合中的元素,key和value的数据类型可以相同也可以不同
(3)Map集合中的元素,key不能重复,value可以重复
(4)Map集合中的元素,key和value一一对应
2.常用子类1:HashMap集合
【1】HashMap<K,V> 位置:util包中
【2】实现了Map<K,V>接口
【3】特点:
(1)底层是哈希表:插叙速度非常快
- jdk1.8之前:数组+链表
- jad1.8之后:数组+链表 / 数组+红黑树(链表的长度超过8)(提高查询速度)
(2)无序,储存元素和取出元素的顺序有可能不一致
(3)线程不安全的集合(多线程)(速度快)
3.常用子类2:LinkedHashMap集合
【1】LinkedHashMap<K,V> 位置:util包中
【2】继承了HashMap<K,V>集合
【3】特点:
(1)底层是哈希表+链表(保证迭代的顺序)
(2)有序的集合:存储元素和取出元素的顺序是一致的
(3)key也不允许重复
4.常用方法
【1】添加:put
//把指定的key-value添加到Map集合中
//返回值:key不重复,返回值V是null
// key重复,会使用新的value替换map中重复的value,返回被替换的value值
// 一般情况下返回值无需接收
public V put(K key, V value)
【2】获取:get
//根据指定的key,在Map集合中获取对应的值
//返回值:key存在,V返回对应的value值
// key不存在,V返回null
public V get(Object key)
【3】删除:remove
//把指定key所对应的key-value元素删除
//返回值:key存在,V返回被删除的value
// key不存在,V返回null
//注意:使用Integer获取返回值,原因使用int会引起空指针异常
public V remove(Object key)
【4】判断是否包含key:containsKey
//判断集合中是否包含指定的key
//包含返回true,不包含返回false
boolean containsKey(Object key)
5.遍历方法1(key找value)的步骤
【1】keySet方法
把Map集合中所有key取出来存储到Set集合中
Set<K> keySet()
【2】使用迭代器/增加for 遍历Set集合
获取Map集合的每一个key
【3】get(key) 通过key获取value
for (String key : map.keySet()) {
Integer value = map.get(key);
}
6.Entry 键值对(key-value)对象
【1】Map.Entry<K,V>:Map接口中有一个内部接口Entry
【2】作用:Map集合一创建,那么就会在Map集合中创建一个Entry对象,用来记录健和值(键值对对象,键与值的映像关系)
【3】entrySet方法
把Map集合内部多个Entry对象取出来存储到一个Set集合中
Set<Map.Entry<K,V>> entrySet()
【4】Entry对象中的方法1:getKey()
【5】Entry对象中的方法2:getValue()
7.遍历方法2(Entry对象遍历)步骤
【1】使用Map集合中方法entrySet把Map集合中多个Entry对象取出来,存储到一个Set集合中
【2】遍历Set集合,获取每一个Entry对象
【3】使用Entry对象中的方法getKey/getValue获取键与值
8.hashMap存储自定义类型key值
Map集合保证key是唯一的:
作为key的元素,必须重写hashCode方法和equals方法,保证唯一的key
9.子类3:Hashtable集合
【1】位置:util包中
【2】实现了Map<K,V>接口
【3】特点
(1)底层也是一个哈希表
(2)线程安全的集合(单线程)(速度慢)
(3)不能存储null值,null键
【4】Hashtable和Vector一样已经被取代了(hashMap)
【5】但Hashtable的子类properties集合依然有使用
【6】properties集合是唯一一个和IO流相结合的集合
10.jdk9对集合添加的优化
【1】List接口,Set接口,Map接口:里面添加了一个静态方法of,可以一次性给集合添加多个元素
【2】前提:当集合中存储元素的个数已经确定了,不再改变是使用
【3】注意事项:
(1)of方法只使用于List接口,Set接口,Map接口,不实用于接口是实现类
(2)of方法的返回值是一个不能改变的集合
不支持操作异常:UnsupportedOperationException
(3)set接口和Map接口不能有重复的元素
非法参数异常:IllegalArgumentException
【4】使用:
List<String> list = List.of("a","b","c","d","c","a")
番外02:Debug追踪
【1】Debug调试程序:可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
【2】使用方式:
(1)行号的右边,鼠标左键单机,添加断点(每个方法的第一行,哪有bug添加到哪里)
(2)右键选择debug执行程序
(3)程序就会停留在添加的第一个断点处
【3】执行程序:
F8:逐行执行程序
F7:进入方法中
Shift+F8:跳出方法
F9:跳到下一个断点,如果没有下一个断点,就结束程序
Control+F2:退出debug模式,停止程序
Console:切换到控制台
14异常
1.概述
【1】含义:程序在执行过程中,出现非正常的情况,最终导致JVM的非正常停止
【2】异常:本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
【3】异常指的不是语法错误,若是语法错误,编译不会通过
2.异常体系&分类
【1】根类:java.lang.Throwable
【2】子类1:java.lang.Error 严重的错误,需要改源代码
【3】子类2:java.lang.Exception 编译期异常
【4】java.lang.Exception的子类RuntimeException运行期异常
【5】Error和Exception区别
3.异常产生过程的解析
【1】访问数组3索引,而数组没有3索引,jvm会检测出程序出现异常
jvm会做两件事:
(1)根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的内容,原因,位置
new ArrayIndexOutOfBoundsException(“3”)
(2)在getElement方法中,没有异常的处理逻辑(try…catch),那么jvm会把异常对象抛出给main方法来处理
【2】给了main方法,main方法也没有处理异常的逻辑,所以再抛给main方法的调用者jvm处理
【3】jvm接收到异常对象,做了两件事:
(1)把异常对象的内容,原因,位置 用红色打印在控制台
(2)jvm会终止当前java程序(中断程序)
4.throw关键字
【1】作用:在指定的方法中抛出指定的异常
【2】使用格式:
throw new xxxException("异常产生的原因")
TIPS:
【1】必须写在方法的内部
【2】new的对象必须是Exception或其子类对象
【3】抛出指定的异常对象,必须处理这个异常对象
- 如果创建的是RuntimeException或其子类,可以不处理,让jvm来处理
- 如果创建的是编译异常,必须处理这个异常,throw/try…catch
TIPS:
【1】必须对传递过来的参数进行合法性的校验
【2】若不合法,必须抛出异常,告知方法的调用者参数有问题
//NullPointerException是运行期异常,不用处理
if (arr == null){
throw new NullPointerException("传递的数组值为null");
}
//ArraryOutOfBoundsException也是运行期异常,不用处理
if (index < 0 || index > arr.length-1){
throw new ArraryOutOfBoundsException("索引越界");
}
5.Objects非空判断方法
//Objects类中的静态方法(源码)
public static <T> T requireNonNull(T obj) {
if (obj == null){
throw new NullPointerException();
}
}
//使用
Objects.requireNonNull(obj1);
Objects.requireNonNull(obj1,"传递的对象值为null");
6.异常处理方式1:声明异常throws关键字
【1】作用:方法内部抛出异常对象时,我们必须处理这个异常对象,使用throws关键字,可以把异常对象声明抛出给方法的调用者处理,最终给jvm处理
【2】使用格式:方法声明时使用
修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException{
throw new AAAException("");
throw new BBBException("");
}
TIPS:
【1】必须写在方法声明处
【2】声明的异常必须是Exception或其子类
【3】方法内部如果抛出多个异常对象,那么throws后边必须也声明多个异常
【4】如果抛出的多个异常对象有子父类关系,声明父类异常即可
【5】调用了一个声明抛出异常的方法,我们就必须处理声明的异常
- 要么继续使用throws抛出,最终交给jvm
- 要么try…catch自己处理异常
7.异常处理方式2:捕获异常try…catch
【1】throws的缺陷:后续代码不会执行
【2】格式:
try {
//可能产生异常的代码
} catch (/*定义一个变量,来接收try抛出的异常对象*/){
//异常的处理逻辑,一般把异常信息记录到日志中
} catch (异常类名 变量名){
} ...
TIPS:
【1】try中可能会抛出多个异常对象,那么就可以用多个catch来处理这些异常对象
【2】
- 如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch后,继续执行之后的代码
- 如果try中没产生异常,不会执行catch中的异常处理逻辑,执行完毕try后,直接执行之后的代码
8.Throwable类中三个异常处理的方法
【1】位置:lang包中
【2】Error和Exception的父类
【3】方法1:getMessage 返回throwable简短描述
【4】方法2:toString 返回throwable详细消息字符串
【5】方法3:printStackTrace jvm打印异常对象,默认调用此方法,异常信息最全面
【6】在catch中使用,可直接对象名.方法名(为父类)
9.finally代码块
【1】格式:
try {
//可能产生异常的代码
} catch (/*定义一个变量,来接收try抛出的异常对象*/){
//异常的处理逻辑,一般把异常信息记录到日志中
} catch (异常类名 变量名){
} ... {
} finally {
}
【2】无论是否出现异常,finally中代码块都会实现
TIPS:
【1】不能单独使用,必须和try一起
【2】一般用于资源释放(资源回收),无论程序是否有异常,最后都要资源释放(IO)
10.多个异常使用捕获该如何处理
【1】多个异常分别处理
//多个try,多个catch
try {
} catch ( ){
}
try {
} catch ( ){
}
【2】多个异常一次捕获,多次处理
//一个try多个catch
try {
} catch ( ){
} catch ( ){
}
TIPS:
catch里定义的变量,如果有子父类关系,那么子类异常变量必须写在父类的上边
【3】多个异常一次捕获,一次处理
//一个try一个catch
try {
} catch (Exception e){
}
11.finally中有return语句
永远返回finally中的结果,所以要避免写这种情况
12.子父类异常
【1】如果父类抛出多个异常,子类重写父类方法时,抛出相同的异常/抛出父类异常的子类(异常的子类)/不抛出异常
【2】父类方法没有抛出异常,子类重写时,也不能抛出异常,只能捕获处理,不能声明抛出
【3】总结:父类异常是什么样,子类异常就什么样
13.自定义异常类
【1】原因:java提供的异常类不够使用
【2】格式
public class xxxException extends Exception/RuntimeException {
//空参数构造方法
public xxxException() {
super();
}
//带异常信息构造方法
//所有异常类都有带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理
public xxxException(String message) {
super(message);
}
}
TIPS:
【1】以Exception结尾
【2】必须继承Exception或者RuntimeException
- 继承Exception:编译期异常,必须处理(两种方法)
- 继承RuntimeException:运行期异常,无需处理,交给jvm
15多线程
1.并发和并行
【1】并发:指两个或多个时间同一时间段内发生(交替执行)
【2】并行:指两个或多个时间同一时刻发生(同时发生)
2.线程和进程
【1】硬盘:永久存储ROM
【2】内存:所用应用程序执行时需要进入内存中,临时存储RAM
【3】进程:进入到内存中的一个应用程序
【4】CPU:对数据进行计算,指挥电脑中的软件和硬件干活
CPU的分类:
(1)AMD:
(2)Inter:Inter Core i7 8866(4核心 8线程)
8线程:可以同时执行八个任务
【4】线程:
- 线程属于进程,是进程中的一个执行单元,负责程序的执行
- 一个程序进入内存后至少有一个进程,一个进程可以有多个线程
- 点击功能执行,会开启一条应用程序到cpu的执行路径
- cpu可以通过路径执行功能,路径叫做线程
【5】多线程好处
(1)效率高
(2)线程间互不影响
3.线程调度
(1)分时调度:所有线程轮流使用CPU,平均分配占用CPU的时间
(2)抢占式调度:优先级高的线程,占用CPU的时间长(java)
4.主线程
【1】主线程:执行(main)方法的线程
【2】单线程:java程序中只有一个线程
【3】程序从main方法开始从上倒下依次执行
5.创建多线程程序的方式1:创建Thread类的子类
【1】位置:Thread类在lang包下
【2】Thread类是描述线程的类,想要实现多线程程序必须继承Thread类
【3】实现步骤:
(1)创建一个Thread类的子类
(2)在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
Option+Enter
(3)创建Thread类的子类对象
(4)调用Thread类中的void start()方法,开启新的线程,执行run方法
(5)同一优先级,随意选择一个执行
TIPS:
【1】结果是两个线程并发的运行(当前线程(main线程)和另一个线程(创建新线程,执行run方法))
【2】多次启动一个线程是非法的。当线程结束后,不能再启动
6.多线程的原理&内存图解
【1】原理:
【2】内存图
7.Thread类常用方法
【1】获取线程名称getname
(1)使用步骤&格式
//现获取当前正在执行的线程
static Thread currentThread()
//返回该线程的名称
String getname()
Thread.currentThread().getname();
(2)线程名称
主线程:main
新线程:Thread-0,Thread-1
【2】设置线程名称
(1)方法1 setnmae
//改变线程的名称,使它的参数和name相同
void setname(String name)
(2)方法2
// 创建带参数的构造方法,参数传递线程的名称,再调用父类的
//带参构造方法,把线程明晨传递给父类,让父类给子线程取名字
//空参数构造方法,MyThread子线程类名
public Mythread(){};
//带参数构造方法
public Mythread(String name){
super(name);
};
【3】暂停线程sleep方法
//使当前正在执行的线程以指定毫秒数暂停
public static void sleep(long millis)
8.创建多线程程序的方式2:实现runnable接口
【1】位置:runnable接口在lang包下
【2】应该由那些打算通过某一线程执行其实例的类来实现,类必须定义一个称为run的无参数方法
【3】Thread类的的构造方法
//分配新Thread对象
Thread(Runnable target)
//分配新的Thread对象
Thread(Runnable target, String name)
【4】实现步骤
(1)创建一个Runnable接口的实现类
(2)在实现类中重写Runnable接口的run方法,设置线程任务
(3)创建一个Runnable接口的实现类对象
(4)创建Thread类对象(构造方法中传递(3)Runnable接口的实现类对象)
(5)调用Thread类中的start方法,开启线程执行run方法
9.创建方法的区别
实现Runnable接口创建多线程程序的好处:
(1)避免单线程的局限性:
- 一个类只能继承一个类,类继承Thread类就不能继承其他的类
- 实现Runnable接口好可以继承其他类,实现其他接口
(2)增强了程序的扩展性,降低了程序的耦合性(解耦)
- 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
- 实现类中重写了run方法,用来设置线程任务
- 创建Thread类对象,调用start方法:用来开启新的线程
10.匿名内部类创建线程
【1】匿名内部类的作用:简化代码
- 把子类继承父类,重写父类的方法,创建子类对象 合成一步完成
- 把实现类实现接口,重写接口中的方法,创建实现类对象 合成一步完成
【2】匿名内部类最终产物:子类/实现类对象(没有名字)
【3】格式
new 父类/接口(){
// 重写方法
};
//创建方法1
new Thread(){
//重写run方法,设置线程任务
}.start();
//创建方法2(多态)
Runnable r = new Runnable(){
//重写run方法,设置线程任务
};
new Thread(r).start();
//2的简化板
new Thread(new Runnable(){
//重写run方法,设置线程任务
}).start();
16线程安全
1.概述
【1】单线程不会出现安全问题
【2】多线程共享数据会出现问题
2.出现安全问题的原理
3.解决方法一:同步代码块
【1】格式:
synchronized(同步锁){
//需要同步操作的代码
}
TIPS:
【1】通过代码块中的锁对象,可以使用任意的对象
【2】但是必须保证多个线程使用的锁对象是同一个
【3】锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行
【2】原理:
4.解决方法二:同步方法
【1】使用步骤:
(1)把访问共享数据的代码抽取出来,放到一个方法中
(2)在方法中添加synchronized修饰符
【2】格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
//访问共享数据的代码抽取出来
}
【3】同步方法的锁对象为实现类对象new RunnableImpl() (this)
【4】静态同步方法
(1)格式:
修饰符 static synchronized 返回值类型 方法名(参数列表){
//访问共享数据的代码抽取出来
}
(2)静态同步方法的锁对象不是this,因为this是创建对象后产生的,而静态方法优先于对象。静态同步方法的锁对象是本类的class属性(class文件对象(反射))
本类名称.class
5.解决方法三:Lock锁
【1】位置:java.util.concurrent.locks.lock接口
【2】java.util.concurrent.locks.ReentrantLock implements Lock实现了Lock接口
【3】Lock接口中的方法
(1)void lock() 获取锁
(2)void unlock() 释放锁
【4】使用步骤
(1)在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();
(2)在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
(3)在可能会出现安全问题的代码前调用Lock接口中的方法unlock释放锁
l.unlock();
无论程序是否异常都释放锁
17线程状态
1.概述
2.线程状态的类别
【1】TimeWaiting计时等待状态(sleep/wait)
【2】Blocked锁阻塞状态
【3】Waiting无限等待状态
(1)Object类中的方法wait
在其他线程调用此对象的notify方法或notifyAll方法前,导致当前线程等待
(2)Object类中的方法notify
唤醒在此对象监视器上等待的单个线程
会继续执行wait方法之后的代码
【4】进入TimeWaiting计时等待状态有两种方法:
(1)sleep方法
(2)wait(long m)方法,如果在毫秒值结束后,还没有被唤醒,会自动醒来
【5】唤醒的方法有两种:
(1)notify方法 随机唤醒一个
(2)notifyAll方法
3.等待唤醒机制:线程间通信
【1】概念:多个线程在处理同一个资源,但处理的动作(线程的任务)不同
【2】为什么要处理通信:默认情况CPU随机切换线程
【3】如何保证通信有效利用资源:等待唤醒机制
4.等待唤醒机制的概述
【1】重点:有效利用资源(生产一个包子,吃一个包子)
【2】使用方法:wait/notify/notifyAll
TIPS:
【1】只通知了一个等待的线程,被通知线程也不能立刻恢复执行(看CPU状态)
【2】wait/notify方法必须以同一个锁调用
【3】wait/notify方法都属于Object类的方法,任何一个类都可以使用
【4】wait/notify方法必须在同步代码块或同步方法中使用
18线程池
1.概述
【1】容器—>集合(LinkedList< Thread >)
【2】
//当程序第一次启动的时候,可以创建多个线程保存到一个集合中
//添加
add(new Thread(xxxx));
//当我们想要使用线程时,从集合中取出使用
//使用(线程只能被一个任务使用)
Thread t = list.remove(0);
Thread t = linked.removefirst(0);
//当使用完毕后,把线程归还给线程池
list.add(t);
linked.addLast(t);
//这是一个队列
//jdk1.5之后自带线程池
【3】好处
(1)降低资源消耗
(2)提高响应速度
(3)提高线程可管理性
2.使用
【1】java.util.concurrent.Executors类
生产线程池的工厂类,用来生产线程池
【2】Executors类中的静态方法:
//创建一个可重用固定线程数的线程池
//参数:int nThreads:创建线程池中包含的线程数量
//返回值:返回ExecutorService接口的实现类对象,我们可以使用
// ExecutorService接口接收(面向接口编程)
static ExecutorService newFixedThreadPool(int nThreads)
【3】java.util.concurrent.ExecutorService 线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
//提交一个Runnable任务用于执行
submit(Runnable task)
//关闭/销毁线程池
void shutdown()
【4】使用步骤:
(1)使用线程池工厂类java.util.concurrent.Executors中的静态方法newFixedThreadPool生产一个指定线程数量的线程池
(2)创建一个类实现Runnable接口,重写run方法,设置线程任务
(3)调用ExecutorsService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
(4)调用ExecutorsService中的方法shutdown,销毁线程池(不建议执行)
19Lambda表达式
1.函数式编程思想的概述
【1】面向对象的思想:做一个事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
【2】函数式编程的思想:只要能获取结果就可以了,谁去做,怎么做都不重要,重视结果不重视过程
2.Runnable代码的冗余
【1】Thread类需要Runnable接口作为参数,用抽象的run方法指定任务的核心
【2】为了指定run的方法体,不得不需要Runnable接口的实现类
【3】为了省去创建一个实现类,不得不使用匿名内部类
【4】必须覆盖重写抽象run方法
【5】实际上只用方法体才是关键
3.编程思想的转换:Lambda表达式
使用Lambada表达式 实现多线程
new Thread(()->{
//
}
).start();
4.Lambda标准格式
【1】好处:
(1)省去实现类的定义
(2)匿名内部类的语法太复杂了
【2】标准格式的三部分:
(1)一些参数
(2)一个箭头
(3)一段代码
【3】格式:
(参数列表)->{一些重写方法的代码}
【4】解释说明格式
():接口中抽象方法的参数列表,没有参数就空着;有参数使用多个逗号分隔
->:传递,把参数传递给{}方法体
{}:重写接口的抽象方法的方法体
5.Lambda省略格式
【1】可推导,可省略:凡是根据上下文推导出来的内容,都可以省略书写
【2】可以省略的内容
(1)(参数列表):数据类型可以省略
(2)(参数列表):括号中的参数如果只有一个,类型和括号都可以省略
(3){一些代码}:如果{}代码只有一行,无论是否有返回值,都可以省略 大括号,return,分号
TIPS:
要省略三个必须一起省略
20File类
1.概述
【1】位置:java.io.File
【2】作用:文件和目录路径名的抽象表现形式
【3】java把电脑中的文件和文件夹/目录 封装为了一个File类,我们可以使用File类对文件和文件夹操作
【4】可以使用File类的方法
(1)创建文件/文件夹
(2)删除文件/文件夹
(3)获取文件/文件夹
(4)判断文件/文件夹是否存在
(5)对文件夹进行遍历
(6)获取文件大小
【5】File类与系统无关:任何操作系统都可以使用这个类中的方法
【6】重点:File/Directory/path
2.静态成员变量
//与系统相关的路径分隔符字符,为方便起见,表示为字符串(:冒号)
public static final String pathSeparator
//与系统相关的路径分隔符字符
public static final char pathSeparatorChar
//与系统相关的默认名称分隔符(文件名称分隔符),以方便的方式表示为字符串(/正斜杠)
public static final String separator
//与系统相关的默认名称分隔符
public static final char separatorChar
操作路径:路径不能写死(系统不同,符号不同)
“C:”+File.separator+“develop”+File.separator+“a”+File.separator+“a.txt”
3.绝对路径&相对路径
【1】绝对路径:完整路径(以盘符开始的路径)
【2】相对路径:简化的路径(相对当前项目的根目录,可以省略)
TIPS:
【1】路径不区分大小写
【2】路径中的文件名称分隔符为/ windows中为反斜杠\ 需要转义\
4.构造方法
【1】构造方法1
//参数:pathname:字符串的路径名称
// 1.路径可以以文件或文件夹结尾
// 2.路径可以是绝对路径或相对路径
// 3.路径可以存在也可以不存在
//创建File对象,只是把字符串路径封装为File对象
// 不考虑路径的真假情况
File(String pathname)
【2】构造方法2
//根据parent路径名字字符串和child路径名字字符串创建
// 一个新的File实例
//参数:把路径分为了两部分
// String parent:父路径
// child:子路径
//好处:1.父路径和子路径可以反复书写,使用起来非常的灵活
// 2.父路径和子路径都可以变化
File(String parent, String child)
【3】构造方法3
//根据parent路径名字字符串和child路径名字字符串创建
// 一个新的File实例
//好处:1.父路径和子路径可以反复书写,使用起来非常的灵活
// 2.父路径是File类型
// 可以使用File类的方法对路径进行操作,再使用路径创建对象
File(File parent, String child)
5.常用方法
【1】获取
//返回此File的绝对路径名称字符串
//获取构造方法中传递的路径:无论路径是绝对的还是相对的
// 返回的都是绝对路径
public String getAbsolutePath()
//将此File转换为路径名字字符串
//获取构造方法中传递的路径
public String getPath()
//返回由此File表示的文件或文件夹名称
//获取构造方法传递路径的结尾部分(文件/文件夹)
public String getName()
//返回由此File表示的文件的长度
//获取构造方法指定的文件的大小,字节为单位
//注意:文件夹没有大小概念
// 如果构造方法中给出的路径不存在,length方法返回0
public long length()
【2】判断
//判断构造方法中的路径是否存在
//存在:true
//不存在:false
public boolean exists()
//判断构造方法中给定的路径是否以文件夹结尾
//是:true
//否:false
public boolean isDirectory()
//判断构造方法中给定的路径是否以文件结尾
//是:true
//否:false
public boolean isFile()
//注意:1.电脑的硬盘中只有文件和文件夹,这两个方法互斥
// 2.这两个方法使用前提:路径必须存在,否则都返回false
【3】创建或删除
//当且仅当具有该名称的文件尚不存在时,创建一个新的空文件
//创建文件的路径和名称在构造方法中给出(构造方法的参数)
//返回值:布尔值
// true:文件不存在,创建
// false:文件存在,不会创建
//注意:1.只能创建文件不能创建文件夹
// 2.创建文件的路径必须存在
//声明抛出了IOException,我们调用这个方法就必须处理这个异常
// 要么throws,要么try...catch
public boolean createNewFile()
//删除由此File表示的文件或文件夹
//返回值:布尔值
// true:删除成功
// false:文件夹中有内容,构造方法中的路径不存在
//注意:delete方法直接在硬盘删除,不走回收站
public boolean delete()
//创建由此File表示的目录,创建单级空文件夹
//创建文件的路径和名称在构造方法中给出(构造方法的参数)
//返回值:布尔值
// true:文件夹不存在,创建
// false:文件夹存在,不会创建;构造方法中给出路径不存在
//注意:1.只能创建文件夹不能创建文件
// 2.创建文件的路径必须存在
public boolean mkdir()
//创建由此File表示的目录,包括任何必需但不存在的父目录
//既可以创建单级文件夹,也可以创建多级文件夹
public boolean mkdirs()
6.文件夹的遍历
【1】方法1
//返回一个String数组,表示该File目录中的所有子文件或文件夹
//可以返回隐藏的文件和文件夹
public String[] list()
【2】方法2
//返回一个File数组,表示该File目录中的所有的子文件或文件夹
public File[] listFiles()
TIPS:
【1】list方法和listFiles方法遍历的是构造方法中给出的文件夹
【2】如果构造方法中给出的路径不存在,会抛出空指针异常
【2】如果构造方法中给出的路径不是文件夹,会抛出空指针异常
7.FileFilter过滤器
【1】在File类中有两个和ListFiles重载的方法,方法的参数传递的就是过滤器
【2】方法1:java.io.FileFilter接口
//java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器
//作用:用来过滤文件(File对象)
File[] listFiles(FileFilter filter)
//抽象方法:用来过滤文件的方法
//测试指定抽象路径是否应该包含在某个路径名列表中
//参数:
//filename:使用ListFiles方法遍历目录,得到的e每一个文件对象
boolean accept(File pathname)
【3】方法2:java.io.FilenameFilter接口
//java.io.FilenameFilter接口:实现此接口的实例可用于
// 过滤器文件名
//作用:用来过滤文件名称
File[] listFiles(FileFilter filter)
//抽象方法:用来过滤文件的方法
//测试指定文件是否应该包含在某一文件列表中
//参数:
//dir:构造方法中传递的被遍历的文件夹
//name:使用ListFiles方法遍历目录,得到的每一个文件/文件夹名称
boolean accept(File dir, String name)
//需要封装
new File(dir, name)
【4】注意:
两个过滤器接口没有实现类,需要我们自己写实现了,重写过滤的方法accept,自定义过滤的规则
【5】使用:
(1)创建过滤器FileFilter的实现类,重写accpet方法
(2)调用
//传递过滤器对象
File[] files = dir.listFiles(new FileFilterImpl());
【6】需要明白的两件事:
(1)过滤器的accpet方法是谁调用的:
(2)accpet方法的参数pathname是什么
【7】listFiles方法做了三件事:
(1)listFiles方法会对构造方法中传递的目录进行遍历,获取目录中的每一个文件/文件夹---->封装为File对象
(2)listFiles方法会调用参数传递的过滤器中的方法accept
(3)listFiles方法会把遍历得到的每一个File对象传递给accpet方法的参数pathname
【8】accpet的返回值是一个布尔值
- true会把传递的File对象保存到数组中
- false不会吧传递的File对象保存到数组中
【9】过滤的规则:
判断是不是想要的结果:
是返回true,否则返回false
【10】Lambda的优化
File[] files = dir.listFiles((dir,name)->
//过滤规则(没有return和分号)
);
番外03:递归
1.概述
概念:方法自己调用自己
2.分类
【1】直接递归:A调用A
【2】间接递归:A调用B,B调用C,C调用A
3.注意事项
【1】保证递归停下来
- 一定要有条件限定,保证递归能停下来,否则会栈内存溢出
- 当一个方法调用其他方法时,被调用方法没有执行完毕,当前方法会一直等待调用的方法执行完毕,才会执行
【2】递归虽然有条件限定,但是次数也不能太多,否则会栈内存溢出
【3】构造方法,禁止递归
4.使用前提
当调用递归方法时,方法的主体不变,每次调用方法的参数不同,可以使用递归
21IO流
1. 概念&分类
【1】输入:硬盘中的数据,读取到内存中使用
【2】输出:内存中的数据,写入到硬盘中保存
【3】分类:顶级父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流< br /> InputStream | 字节输出流< br /> OutputStream |
字符流 | 字符输入流< br /> Reader | 字符输出流< br /> Writer |
21.1字节流
1.一切皆为字节
计算机中存储的任意数据吗都是字节
2.字节输出流 OutputStream类
【1】位置:java.io
【2】概述:OutputStream类(抽象类):所有字节输出流的顶层父类
【3】定义了一些子类共性的成员方法
//关闭此输出流并释放与此流相关联的任何系统资源
public void close()
//刷新此输出流并强制任何缓冲的输出字节被写出
public void flush()
//将 b.length字节从指定的字节数组写入此输出流
public void write(byte[] b)
//从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public void write(byte[] b, int off, int len)
//将指定的字节输出流
public abstract void write(int b)
3.字节输出流 FileOutputStream类
【1】位置:java.io
【2】概述:文件字节输出流FileOutputStream类:继承了OutputStream
【3】作用:把内存中的数据写入到硬盘的文件中
【4】构造方法:
//创建文件输出流以写入由指定的File对象表示的文件
//参数:写入数据的目的地
// file:目的地是一个文件
public FileOutputStream(File file)
//创建文件输出流以指定的名称写入文件
//参数:写入数据的目的地
// name:目的地是一个文件的路径
public FileOutputStream(String name)
【5】构造方法的作用:
(1)创建一个FileOutputStream对象
(2)根据构造方法中传递的文件/文件路径创建一个空的文件
(3)把FileOutputStream对象指向创建好的文件
4.字节输出流写入数据到文件
【1】原理(内存---->硬盘):
java程序---->jvm(java虚拟机)---->os(操作系统)---->os调用写数据的方法---->把数据写入到文件中
【2】字节输出流的使用步骤【重点】
(1)创建一个FileOutputStream对象,构造方法中传入目的地
(2)调用FileOutputStream对象中的方法write,把数据写入文件中
(3)释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率)(close方法)
5.文件存储的原理
【1】写数据的时候,会把10进制的整数,转换为二进制
【2】硬盘中存储的数据都是字节
1个字节 = 8个比特位
【3】任意文本编辑器(记事本):在打开文件的时候,都会查询编码表把字节转换为字符表示
0-127:查询ASCII码表
其他值:查询系统默认码表(中文系统GBK)
6.字节输出流写多个字节的方法
【1】两种方法
//将 b.length字节从指定的字节数组写入此输出流
//如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表
//如果写的第一个字节是负数,那么第一个字节会和第二个字节
// 组成中文显示(查询系统默认码表)
public void write(byte[] b)
//从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
//把字节数组的一部分写入到文件中
//off:数组开始的索引
//len:写几个字节
public void write(byte[] b, int off, int len)
【2】写入字符的方法,可以用String类中的方法把字符串,转换为字节数组
//把字符串转换为字节数组
byte[] getBytes()
7.字节输出流的续写和换行
【1】续写:使用两个参数的构造方法
//创建文件输出流以写入由指定的 File对象表示的 文件。
//参数:file/name 写入数据的目的地
// append 续写的开关
// true:创建对象不会覆盖源文件,继续在文件的末尾续写
// false:创建新文件覆盖原文件,在新文件中写
public FileOutputStream(File file, boolean append)
//创建文件输出流以指定的名称写入文件
public FileOutputStream(String name, boolean append)
【2】换行:
换行符号:
windows:\r\n
linux:\n
mac:\r \n \r\n
8.字节输入流 InputStream类
【1】位置:java.io
【2】概述:InputStream类(抽象类):所有字节输入流的顶层父类
【3】定义了一些子类共性的成员方法
//关闭此输入流并释放与此流相关联的任何系统资源
public void close()
//从输入流读取数据的下一个字节
public abstract int read()
//从输入流中读取一些字节数,并将它们存储到字节数组 b中
public int read(byte[] b)
9.字节输入流 FileInputStream类
【1】位置:java.io
【2】概述:文件字节输入流FileInputStream类:继承了InputStream
【3】作用:把硬盘的文件中的数据读取到内存中使用中
【4】构造方法:
//创建文件输出流以写入由指定的File对象表示的文件
//参数:读取文件的数据源
// file:是一个文件
public FileInputStream(File file)
//创建文件输出流以指定的名称写入文件
//参数:读取文件的数据源
// name:是一个文件的路径
public FileInputStream(String name)
【5】构造方法的作用:
(1)创建一个FileInputStream对象
(3)把FileInputStream对象指向构造方法中要读取的文件
10.字节输入流一次读取一个字节数据
【1】原理(硬盘---->内存):
java程序---->jvm(java虚拟机)---->OS---->OS读取数据的方法---->读取文件
【2】字节输入流的使用步骤【重点】
(1)创建一个FileInputStream对象,构造方法中绑定要读取的数据源
(2)使用FileInputStream对象中的方法read,读取文件
(3)释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率)(close方法)
【3】
//从输入流读取数据的下一个字节,读取到文件的末尾返回-1
//可以使用循环 while ((len == fis.read()) != -1)
//(char)name 转换成字符
public abstract int read()
【4】原理
11.字节输入流一次读取多个字节数据
//从输入流中读取一些字节数,并将它们存储到字节数组 b中
public int read(byte[] b)
【1】明确两件事:
(1)byte类型数组的作用
(2)返回值int是什么
(1)起缓冲作用,存储每次读取到的多个字节
数组的长度一般定义为1024(1kb)或者1024的整数倍
(2)读取的有效字节个数
【2】 String类的构造方法
//把字节数组转换为字符串
String(byte[] bytes)
//把字节数组的一部分转换为字符串
//offest:开始的索引 length:转换的字节个数
String(byte[] bytes, int offset, int length)
【3】原理
【4】while循环优化
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes))!= -1) {
sout(new String(bytes,0,len));
}
TIPS:
释放资源时,先关闭写的,后关闭读的(如果写完了,肯定读取完毕了)
21.2字符流
字节流存在的问题:读取中文时可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储
1.字符输入流 Reader类
【1】位置:java.io
【2】概述:Reader类(抽象类):所有字符输入流的顶层父类
【3】定义了一些子类共性的成员方法
//读取单个字符并返回
int read()
//一次读取多个字符,将字符读入数组
int read(char[] cbuf)
//释放资源
void close()
2.字符输入流 FileReader类
【1】位置:java.io
【2】概述:文件字符输入流FileReader类:继承了InputStreamReader类继承了Reader类
【3】作用:把硬盘文件中的数据以字符的方式读取到内存中
【4】构造方法:
//参数:读取文件的数据源
// file:一个文件
public FileReader(File file)
//参数:读取文件的数据源
// filename:文件的路径
public FileReader(String filename)
【5】构造方法的作用:
(1)创建一个FileReader对象
(2)把FileReader对象指向要读取的文件
3.字符输入流读取字符数据
【1】字符输入流的使用步骤【重点】
(1)创建一个FileReader对象,构造方法中绑定要读取的数据源
(2)调用FileReader对象中的方法read读取文件
(3)释放资源
【2】参数和字节输入流相同
【3】String类构造方法
//把字符数组转换为字符串
String(char[] value)
//把字符数组的一部分转换为字符串
//offest:开始的索引 length:转换的字节个数
String(char[] value, int offset, int length)
4.字符输出流 Writer类
【1】位置:java.io
【2】概述:Writer类(抽象类):所有字符输出流的顶层父类
【3】定义了一些子类共性的成员方法
//写入单个字符
void write(int c)
//写入字符数组
void write(char[] cbuf)
//写入字符数组的某一部分
abstract void write(char[] cbuf, int off, int len)
//写入字符串
void write(String str)
//写入字符串的某一部分
void write(String str, int off, int len)
//关闭此流,先要刷新他
void close()
//刷新此流的缓冲国
void flush()
5.字符输出流 FileWriter类
【1】位置:java.io
【2】概述:文件字符输出流FileWriter类:继承了OutputStreamWriter类继承了Writer类
【3】作用:把内存中的字符数据写入到硬盘的文件中
【4】构造方法:
//根据给定的File对象构造一个FileWriter对象
//参数:写入数据的目的地
// file:目的地是一个文件
public FileWriter(File file)
//根据给定的文件名构造一个FileWriter对象
//参数:写入数据的目的地
// name:目的地是一个文件的路径
public FileWriter(String fileName)
【5】构造方法的作用:
(1)创建一个FileWriter对象
(2)根据构造方法中传递的文件/文件路径, 创建文件
(3)把FileWriter对象指向创建好的文件
6.字符输出流写入数据到文件
【1】字符输出流的使用步骤【重点】:
(1)创建一个FileWriter对象,构造方法中绑定要写入数据的目的地
(2)调用FileWriter对象中的方法write,把数据写入内存缓冲区中(字符转换为字节的过程)
(3)使用FileWriter对象中的方法flush,把内存缓冲区中的数据,刷新到文件中
(4)释放资源(会先把内存缓冲区中的数据刷新到文件中)
【2】flush方法和close方法的区别:
(1)flush:刷新缓冲区,流对象可以继续使用
(2)close:先刷新缓冲区,然后通知系统释放资源,流对象不能继续使用
7.字符输出流的续写和换行
【1】续写:使用两个参数的构造方法
//创建文件输出流以写入由指定的 File对象表示的文件。
//参数:file/name 写入数据的目的地
// append 续写的开关
// true:创建对象不会覆盖源文件,继续在文件的末尾续写
// false:创建新文件覆盖原文件,在新文件中写
public FileWriter(File file, boolean append)
//创建文件输出流以指定的名称写入文件
public FileWriter(String name, boolean append)
【2】换行:
换行符号:
windows:\r\n
linux:\n
mac:\r \n \r\n
21IO流(续)
2.try_catch_finally处理流中的异常
【1】格式:
try{
//可能产生异常的方法
}catch(异常类变量 变量名){
//异常的处理逻辑
}finally{
//一定会执行的代码
//资源释放
}
【2】使用:
//提高变量fw的作用域,让finally可以使用
//变量在定义时可以没有值,但使用的时候必须有值
FileWirter fw = null;
try{
FileWirter fw = new FileWriter();
}catch(IOException e){
sout(e);
}finally{
//fw.close()也有异常
//创建对象失败了,fw默认值是努力了,null不能调用,会抛出空指针异常,需要增加一个判断,不是null再把资源释放
if (fw != null){
try{
fw.close();
}catch(异常类变量 变量名){
e.printStacktrace();
}
}
}
3.jdk7&jdk9流的异常处理
【1】jdk7新特性:
在try的后面可以增加一个(),在括号中可以定义流对象,那么合格流对象的作用域就会在try中有效,try中代码执行完毕,会自动把流对象释放,不用finally
【2】格式:
try(定义流对象,定义流对象,...){
//可能产生异常的方法
}catch(异常类变量 变量名){
//异常的处理逻辑
}
【3】jdk9新特性:
在try的前面可以定义流对象,在try后面的()中可以直接引入流对象的名称(变量名)
在try代码执行完毕之后,流对象也可以释放,不用finally
【4】格式:
A a = new A();
B b = new B();
try(a,b){
//可能产生异常的方法
}catch(异常类变量 变量名){
//异常的处理逻辑
}
22属性集(Properties类)
1.概述
【1】位置:java.util
【2】继承了HashTable<k,v>类实现了Map<k,v>接口
【3】Properties类表示一个持久的属性集。Properties可保存在流中或从流中加载。
【4】Properties集合是唯一一个和IO流相结合的集合
(1)可以使用Properties集合中的store方法把集合中的临时数据,持久化写入到硬盘中存储
(2)可以使用Properties集合中的load方法把硬盘中保存的文件(键值对),读取到集合中使用
2.储存数据,遍历取出数据
【1】属性列表中每个键及其对应值都是一个字符串。
Properties集合是一个双列集合,key和value默认都是字符串
【2】Properties集合有一些操作字符串的特有方法
//调用HashTable的方法put
Object setProperty(String key, String value)
//通过key找到value值,此方法相当于Map集合中的get(key)方法
String getProperty(String key)
//返回此属性列表中的键值,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
Set<String> PropertyNames()
3.store方法
【1】格式:
//把集合中的临时数据,持久化写入到硬盘中存储
//参数:out:字节输出流,不能写入中文
// writer:字符输出流,可以写中文
// comments:注释,用来解释说明保存的文件是做什么用的
// 不能使用中文(会产生乱码,默认Unicode编码)
// 一般使用空字符串
void store(OutputStream out, String comments)
void store(Writer writer, String comments)
【2】使用步骤:
(1)创建Properties对象,添加数据
(2)创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
(3)使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
(4)释放资源
4.load方法
【1】格式:
//把硬盘中保存的文件(键值对),读取到集合中使用
//参数:inStream:字节输入流,不能写入中文
// Reader:字符输入流,可以写中文
void load(InStream inStream)
void load(Reader reader)
【2】使用步骤:
(1)创建Properties对象
(2)使用Properties集合对象中的方法load,读取保存键值对的文件
(3)遍历Properties集合
(4)释放资源
TIPS:
【1】存储键值对的文件中,键和值默认的连接符号可以使用=,空格(其他符号)
【2】存储键值对的文件中,可以使用#进行注释,被注释的键值对不会被读取
【3】存储键值对的文件中,键与值默认都是字符串,不用再加引号
23缓冲流(buffered)
1.原理
【1】对基本流的一种增强
【2】提高基本流的效率
BufferedInputStream(new FileInputStream())
int len = fis.read();
2.BufferedOutputStream 字节缓冲输出流
【1】位置:java.io
【2】继承了OutputStream类
【3】继承自父类的共性成员方法:
//关闭此输出流并释放与此流相关联的任何系统资源
public void close()
//刷新此输出流并强制任何缓冲的输出字节被写出
public void flush()
//将 b.length字节从指定的字节数组写入此输出流
public void write(byte[] b)
//从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public void write(byte[] b, int off, int len)
//将指定的字节输出流
public abstract void write(int b)
【4】构造方法:
//创建一个新的缓冲输出流,以将数据写入指定的底层输出
//参数:out:字节输出流
// 我们可以传递FileOutputStream,缓冲流会为它增加一个缓冲区,提高FileOutputStream的写入效率
// size:指定缓冲流内部缓冲区的大小,不指定则为默认大小
BufferedOutputStream(OutputStream out)
//创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出
BufferedOutputStream(OutputStream out, int size)
【5】使用步骤【重点】
(1)创建FileOutputStream对象,构造方法中绑定要输出的目的地
(2)创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象的效率
(3)使用BufferedOutputStream对象中的方法write,把数据写入到内部的缓冲区中
(4)使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据刷新到文件中(可省略)
(5)释放资源bos(会先调用flush方法刷新数据)
3.BufferedInputStream 字节缓冲输入流
【1】位置:java.io
【2】继承了InputStream类
【3】继承自父类的共性成员方法:
//关闭此输入流并释放与此流相关联的任何系统资源
public void close()
//从输入流读取数据的下一个字节
public abstract int read()
//从输入流中读取一些字节数,并将它们存储到字节数组 b中
public int read(byte[] b)
【4】构造方法:
//创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用
//参数:in:字节输入流
// 我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FileInputStream的读取效率
// size:指定缓冲流内部缓冲区的大小,不指定则为默认大小
BufferedInputStream(InputStream in)
//创建一个指定缓冲区大小的BufferedInputStream并保存其参数,即输入流in,以便将来使用
BufferedInputStream(InputStream in, int size)
【5】使用步骤【重点】
(1)创建FileInputStream对象,构造方法中绑定要读取的数据源
(2)创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的效率
(3)使用BufferedInputStream对象中的方法read,读取文件
(4)释放资源bis
4.BufferedWriter 字符缓冲输出流
【1】位置:java.io
【2】继承了Writer类
【3】继承自父类的共性成员方法:
//写入单个字符
void write(int c)
//写入字符数组
void write(char[] cbuf)
//写入字符数组的某一部分
abstract void write(char[] cbuf, int off, int len)
//写入字符串
void write(String str)
//写入字符串的某一部分
void write(String str, int off, int len)
//关闭此流,先要刷新他
void close()
//刷新此流的缓冲国
void flush()
【4】构造方法:
//创建一个使用默认大小输出缓冲区的缓冲字符输出流
//参数:out:字符输出流
// 我们可以传递FileWriter,缓冲流会为FileWriter增加一个缓冲区,提高FileWriter的写入效率
// size:指定缓冲流内部缓冲区的大小,不指定则为默认大小
BufferedWriter(Writer out)
//创建一个使用指定大小输出缓冲区的缓冲字符输出流
BufferedWriter(Writer out, int sz)
【5】特有的成员方法:
//写一个行分隔符,根据不同的系统获取不同的行分隔符
void newLine()
【6】使用步骤【重点】
(1)创建FileWriter对象,构造方法中绑定要输出的目的地
(2)创建BufferedWriter对象,构造方法中传递FileWriter对象,提高FileWriter对象的效率
(3)使用BufferedWrite对象中的方法write,把数据写入到内存缓冲区中
(4)使用BufferedWrite对象中的方法flush,把内部缓冲区中的数据刷新到文件中(可省略)
(5)释放资源bw(会先调用flush方法刷新数据)
5.BufferedReader 字符缓冲输入流
【1】位置:java.io
【2】继承了Reader类
【3】继承自父类的共性成员方法:
//读取单个字符并返回
int read()
//一次读取多个字符,将字符读入数组
int read(char[] cbuf)
//释放资源
void close()
【4】构造方法:
//创建一个使用默认缓冲区大小的缓冲字符输入流
//参数:in:字符输入流
// 我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
// size:指定缓冲流内部缓冲区的大小,不指定则为默认大小
BufferedReader(Reader in)
//创建一个使用指定缓冲区大小的缓冲字符输入流
BufferedReader(Reader in, int sz)
【5】特有的成员方法
//读取一行数据
//行的终止符号:通过下列字符之一即可认为某行已终止
// 换行('\n') 回车('\r') 回车后直接跟着换行('\r\n')
//返回值:包含该行内容的字符串,不包含任何终止符,如果已到达流末尾,返回null
String readLine()
【6】使用步骤【重点】
(1)创建FileReader对象,构造方法中绑定要读取的数据源
(2)创建BufferedReader对象,构造方法中传递BufferedReade对象,提高BufferedReade对象的效率
(3)使用BufferedReade对象中的方法read/readLine,读取文本
(4)释放资源
24转换流
1.字符编码和字符集
【1】编码:按照某种规则将字符存储到计算机中(字符->字节)(字符能看懂,字节看不懂)
【2】解码:将存储在计算机中的二进制数按某种规则解析显示出来(字节->字符)
【3】字符编码:生活中文字和计算机中二进制的对应规则
【4】字符集:系统支持所有字符的集合(各个国家的文字,标点,图形符号,数字)
【5】常用字符集:
(1)ASCII字符集:基于拉丁字母,用于显示现在英语,基本符号
基本:7位(bits)表示一个字符,共128个。(第一位0表示正数)
扩展:8位(bits)表示一个字符,共128个。(第一位1)
(2)ISO-8859-1字符集:拉丁码表,显示欧洲使用的语言:荷兰,丹麦,德国,意大利,西班牙
ISO-5559-1使用单字节编码,兼容ASCII编码。
(3)GBxxx字符集:
- GB2312:简体中文码表。一个小于127的字符的意义与原来相同。包含7000多个简体汉字,兼容ASCII编码。
- GBK:最常用的中文码表。使用了双字节编码方案,共收录了 21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节 组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
(4)Unicode字符集 (万国码):
- Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
- 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
- UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以, 我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
- 128个US-ASCII字符,只需一个字节编码。
- 拉丁文等字符,需要二个字节编码。
- 大部分常用字(含中文),使用三个字节编码。
- 其他极少使用的Unicode辅助字符,使用四字节编码。
2.产生的原因
只能读取jdk默认编码(UTF-8),读取其他编码的文件会出现乱码
3.原理
4.OutputStreamWriter类
【1】位置:java.io
【2】继承了Writer类
【3】作用:字符通向字节的桥梁,可以使用指定的编码表(charset)将要写入流中的字符编码成字节。(编码)
【4】继承自父类的成员方法
//写入单个字符
void write(int c)
//写入字符数组
void write(char[] cbuf)
//写入字符数组的某一部分
abstract void write(char[] cbuf, int off, int len)
//写入字符串
void write(String str)
//写入字符串的某一部分
void write(String str, int off, int len)
//关闭此流,先要刷新他
void close()
//刷新此流的缓冲国
void flush()
【5】构造方法
//创建使用默认字符集编码OutputStreamWriter
//参数:out:字节输出流,可以用来写转换之后的字节到文件中
// charsetName:指定编码表名称,不区分大小写
OutputStreamWriter(OutputStream out)
//创建使用指定字符集的OutputStreamWriter
OutputStreamWriter(OutputStream out, String charsetName)
【6】使用步骤
(1)创建OutputStreamWriter对象,构造方法中传递字节输出流和指定编码表名称
(2)使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码)
(3)使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
(4)释放资源
5.InputStreamReader类
【1】位置:java.io
【2】继承了Reader类
【3】作用:字节通向字符的桥梁,可以使用指定的编码表(charset)读取字节并将其解码为字符。(解码)
【4】继承自父类的成员方法
//读取单个字符并返回
int read()
//一次读取多个字符,将字符读入数组
int read(char[] cbuf)
//释放资源
void close()
【5】构造方法
//创建使用默认字符集编码InputStreamReader
//参数:in:字节输入流,可以用来读取文件中保存的字节
// charsetName:指定编码表名称,不区分大小写
InputStreamReader(InputStream out)
//创建使用指定字符集的InputStreamReader
InputStreamReader(InputStream in, String charsetName)
【6】使用步骤
(1)创建InputStreamReader对象,构造方法中传递字节输入流和指定编码表名称
(2)使用InputStreamReader对象中的方法read读取文件
(4)释放资源
TIPS:
构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码
25序列化流&反序列化流
1.概述
2.ObjectOutputSteam 序列化流
【1】位置:java.io
【2】继承了OutputStream类
【3】作用:把对象以流的方式写入到文件中保存
【4】构造方法:
//创建写入指定OutputStream的ObjectOutputStream
//参数: out:字节输出流
ObjectOutputStream(OutputStream out)
【5】特有的成员方法:
将指定对象写入到ObjectOutputStream
void writeObject(Object obj)
【6】使用步骤:
(1)创建一个ObjectOutputStream对象,构造方法中传递字节输出流
(2)使用ObjectOutputStream对象的方法writeObject,把对象写入文件中
(3)释放资源
【7】序列化和反序列化的时候会抛出NotSerializableException没有序列化异常
- 类需要实现java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化和反序列化。
- Serializable接口也叫标记型接口,要进行序列化/反序列化的类必须实现它,它会给类添加一个标记。当进行序列化/反序列化时,就会检测类上是否有这个标记,如果有可以进行,没有会抛出异常。
3.ObjectInputSteam 反序列化流
1】位置:java.io
【2】继承了InputStream类
【3】作用:把文件中保存的对象以流的方式读取出来使用
【4】构造方法:
//创建从指定InputSteam读取的ObjectInputSteam
//参数: in:字节输入流
ObjectInputSteam(InputSteam in)
【5】特有的成员方法:
从ObjectInputSteam读取对象
Object readObject()
【6】使用步骤:
(1)创建一个ObjectInputSteam对象,构造方法中传递字节输入流
(2)使用ObjectInputSteam对象的方法readObject读取保存对象的文件
(3)释放资源
(4)使用读取出来的对象
【7】readObject方法声明抛出ClassNotFoundException(class文件找不到异常)
【8】反序列化的前提:
(1)类必须实现Serializable接口
(2)必须存在类对应的class文件(需要抛出异常)
4.transient关键字(瞬态关键字)
【1】static静态关键字:
- 静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
- 被static修饰的成员变量,不能被序列化,序列化的都是对象
【2】transient瞬态关键字: - 被transient修饰的成员变量,不能被序列化
5.InvalidClassException异常处理
【1】当Class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作失败,抛出InvalidClassException
【2】解决:声明serialVersionUID
static final long serialVersionUID = 321312312L;
26打印流(PrintStream)
1.概述
【1】位置:java.io
【2】PrintStream为其他输出流添加了功能,使它们能够方便的打印各种数据值表现形式
【3】继承了OutputStream类,是一个字节流
2.特点
【1】只负责数据的输出,不负责数据的读取
【2】永远不会抛出IOException
【3】有特有的方法,print,println
void print(任意类型的值)
void println(任意类型的值并换行)
3.构造方法
//输出的目的地是一个文件
PrintStream(File file)
//输出的目的地是一个字节输出流
PrintStream(OutputStream out)
//输出的目的地是一个文件路径
PrintStream(String fileNanme)
4.继承自父类的成员方法
//关闭此输出流并释放与此流相关联的任何系统资源
public void close()
//刷新此输出流并强制任何缓冲的输出字节被写出
public void flush()
//将 b.length字节从指定的字节数组写入此输出流
public void write(byte[] b)
//从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public void write(byte[] b, int off, int len)
//将指定的字节输出流
public abstract void write(int b)
5.注意事项
【1】如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
【2】如果使用自己特有的方法print写数据,写的数据原样输出 97->97
6.使用步骤
【1】创建打印流PrintStream对象,构造方法中绑定要输出的目的地,抛出FileNotFoundException异常
【2】使用write方法
7.改变输出语句的目的地
- 使用System类中setOut方法,可以改变输出语句的目的地改为参数中传递的打印流的目的地
- 把sout导向Printstream对应的目的地打印
//重新分配"标准"输出流
static void setOut(PrintStream out)
27网络编程入门
1.软件结构
【1】C/S结构 Client/Server 客户端/服务器
【2】B/S结构 Browser/Server 浏览器/服务器
2.网络通信协议
【1】网络通信协议:通信协议是对计算机必须遵守的规则,通信双方必须同时遵守,最终完成数据交换。
【2】TCP/IP协议:传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是 Internet最基本、最广泛的协议。4层的分层模型。
物理层:光纤,网线
网络层:核心层,在传输的数据分组,将分组的数据发送到网络中 ARP/IP/ICMP
传输层:使用网络进行通信 TCP/UDP
应用层:应用程序 HTTP/DNS
3.网络通信协议的分类 UDP协议
【1】UDP协议:用户数据报协议(User Datagram Protocol)。无连接通信协议 传输数据时,不需要建立连接。
【2】UDP特点:消耗资源小,通信效率高,数据包的大小限制在64k以内
【3】UDP应用:音频,视频,普通数据的传输(视频会议)
4.网络通信协议的分类 TCP协议
【1】TCP协议:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,传输数据之前,在发送端和接收端建立逻辑连接,提供了两台计算机之间可靠无差错的数据传输。
【2】TCP特点:
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可 靠。
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可 以保证传输数据的安全。
【3】TCP应用:下载文件、浏览网页等。
5.网络编程三要素 协议&IP地址
【1】协议:计算机网络通信必须遵守的规则
【2】IP地址:指互联网协议地址(Internet Protocol Address)
IP地址用来给一个网络中的计算机设备做唯一的编号
【3】IP地址分类:
-
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成 a.b.c.d 的形式,例如 192.168.65.100 。其
中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。 -
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,号称可以为全世界的每一粒沙子编上一个网 址,这样就解决了网络地址资源数量不够的问题。
【4】常用命令
#查看本机ip地址
ifconfig en0
#检查网络是否连通
#ping 空格 IP地址
【5】特殊的IP地址:
本机IP地址: 127.0.0.1 、 localhost 。
6.网络编程三要素 端口号
【1】概述:逻辑端口,我们无法直接看到,但可以使用一些软件查看
【2】产生:
- 当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号
- 或者网络软件打开的时候和系统要指定的端口号
【3】端口号由两个字节组成,取值范围0-65535
Tips:
【1】1024之前的端口号不能使用,已经被系统分配给已知网络软件了
【2】网络的端口号不能重复
【3】必须保证数据准确无误的发送到对方计算机的软件上(IP地址+端口)
【4】常用端口号:
(1)80端口:网络端口
- www.baidu.com:80 正确
- www.baidu.com:70 错误
(2)Mysql端口号:3306
(3)Oracle端口号:1521
(4)Tomcat服务器:8080
28TCP通信程序
1.概述
【1】TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端/服务器(Server)。
【2】面向连接的通信,需要三次握手,建立逻辑连接,才能通信(安全)
【3】步骤:
(1)服务器先启动
(2)服务器不会主动请求客户端
(3)必须客户端主动连接服务器端
(4)两端建立一个逻辑连接
(5)连接中包含一个IO对象
(6)两端可以使用这个IO对象进行通信
(7)通信的数据不仅仅是字符,IO对象是字节流对象
【4】进行一个数据交互需要4个IO流
【5】服务器端必须明确两个事情:
(1)多个客户端可以同时和服务器进行交互,服务器必须明确和哪个客户端进行的交互
- 服务器端有一个方法叫做accpet,可以获取到请求的客户端对象
(2) 多个客户端同时和服务器进行交互,就需要使用多个IO流对象
- 服务器没有IO流,可以获取到请求的客户端对象Socket,使用每个客户端Socket中提供的IO流,和客户端进行交互
- 服务器使用客户端的字节输入流读取客户端发送的数据
- 服务器使用客户端的字节输出流给客户端回写/发送数据
- 服务器使用客户端的流和客户端交互
2.客户端代码的实现:Scoket类
【1】位置:java.net
【2】此类实现客户端套接字
套接字:包含了IP地址和端口号的网络单位
【3】构造方法:
//创建一个流套接字并将其连接到指定主机上的指定端口号
//参数:host:服务器主机的名称/IP地址
// port:服务器的端口号
Socket(String host, int port)
【4】成员方法:
//返回此套接字的输出流
OutputStream getOutputStream()
//返回此套接字的输入流
InputStream getInputStream()
//关闭此套接字
void close()
【5】实现步骤:
(1)创建一个客户端对象Socket,构造方法中绑定服务器的IP地址和端口号
(2)使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
(3)使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
(4)使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
(5)使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
(6)释放资源(Socket)
Tips:
【1】客户端和服务器进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
【2】当创建客户端对象时,就会请求服务器,和服务器经过三次握手,建立连接通路。
- 这时如果服务器没有启动,就会抛出异常
- 如果服务器已经启动,就可以进行交互
3.服务器代码的实现:ServerScoket类
【1】位置:java.net
【2】此类实现服务器套接字
【3】服务器端必须明确一件事:
必须知道是哪个客户端请求的服务器,所以可以使用accpet方法获取到请求的客户端对象Socket
【4】构造方法:
//创建绑定到指定端口号的服务器套接字
ServerScoket(int port)
【5】成员方法:
//侦听并接受到此套接字的连接
Socket accpet()
【6】实现步骤:
(1)创建服务器ServerSocket对象和系统要指定的端口号
(2)使用ServetSocket对象中的方法accept,获取请求的客户端对象Socket
(3)使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
(5)使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
(6)使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
(7)使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
(8)释放资源(Socket,ServerSocket)
4.文件上传的阻塞问题 shutdownOutput方法
【1】原因:
-
fis.read(bytes)读取本地文件,结束标记读取到-1结束。
-
但是不会读取到-1,不会把结束标记写给服务器
-
is.read 读取客户端上传的文件,永远读取不到文件的结束标记,所以会进入阻塞状态,在此一直死循环等待结束标记
【2】解决:socket方法shutdownOutput
上传完文件,给服务器写一个结束标记
//禁用此套接字的输出流
void shutdownOutput()
5.优化
【1】文件名称重复
解决:
自定义一个文件的命名规则:防止同名文件被覆盖
规则:域名+毫秒值+随机数
【2】循环接收
解决:
- 让服务器一直处于监听状态(死循环accept方法)
- 有一个客户端上传文件,就保存一个文件
- 服务器不用关闭
【3】多线程提高效率
解决:
- 有一个客户端上传文件,就开启一个线程,完成文件的上传
- 重写方法时,不能声明抛出异常,需要使用try…catch
6.模拟B/S服务器
【1】客户端:http://127.0.0.1:8080/10/web/index.html
【2】服务器会收到客户端的请求信息
【3】需要读取index.html文件的地址:
地址就是请求信息的第一行:
GET /10/web/index.html HTTP/1.1
【4】使用BufferedReader中的方法readline读取一行
【5】使用String类的方法split切割字符串获array[1]取/10/web/index.html
【6】使用String类的方法substring(1)获取html文件的路径
10/web/index.html
【7】在服务器创建一个本地的字节输入流,根据获取的文件路径,读取html文件
【8】先写三行代码,固定写法
// 写入HTTP协议响应头,固定写法
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content‐Type:text/html\r\n".getBytes());
// 必须要写入空行,否则浏览器不解析
out.write("\r\n".getBytes());
【9】服务器使用网络字节输出流把读取到的客户端(浏览器)显示
29函数式接口
1.概述
【1】概念:有且只有一个抽象方法的接口,接口中可以包含其他方法(静态方法,私有方法,默认方法)
【2】适用于函数式编程,Lambda使用
【3】语法糖是指使用更加方便,但是原理不变的代码语法。例:for-each
【4】@FunctionalInterface注解(给接口添加):检测接口是否是一个函数式接口
【5】Runnable,Comparator接口就是函数式接口
2.使用
【1】一般可以作为方法的参数和返回值类型
【2】参数:
//MyfunctionalInterface是函数式接口
public static void show(MyfunctionalInterface myInter){
myInter.method();
}
psvm {
//调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyfunctionalInterfaceImpl());
//调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
show(new MyfunctionalInterface() {
@override
});
//调用show方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
show(()->{});
}
3.Lambda表达式的延迟执行
【1】原因:有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以 作为解决方案,提升性能。
【2】性能浪费的产生:
- showLog方法,传递的第二个参数是拼接后的字符串
- 先把字符串拼接好,再调用showLog方法
- 如果等级不是1级,就不会传递拼接后的字符串,就出现了浪费
【3】优化: - Lambda的特点:延迟加载
- Lambda的使用前提,必须存在函数式接口
showLog(level, messageBuilder mb) {}
showLog(1,()->{
return msg1+msg2+msg3;
});
4.常用函数式接口1 Supplier接口
【1】位置:java.util.function.Supplier< T >
【2】方法:
//用来获取一个泛型参数指定类型的对象数据
T get()
【3】Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,接口中的get方法就会产生什么类型的数据
【4】使用:
//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
public static String getString(Supplier<String> sup){
return sup.get();
}
psvm {
String s = getString(()->{return "a";});
}
5.常用函数式接口2 Consumer接口
【1】位置:java.util.function.Consumer< T >
【2】方法:
//消费一个指定泛型的数据
void accept(T t)
【3】Consumer接口是一个消费型接口,泛型是什么类型,就可以使用accept方法消费什么类型的数据
【4】使用:
//定义一个方法,方法的参数传递一个字符串姓名和Consumer接口,泛型使用String,可以使用Consumer接口消费字符串的姓名
public static void method(String name, Consumer<String> con){
con.accept(name);
}
psvm {
method("a",(String name)->{
sout(name);
})
}
【5】默认方法 andThen:
作用:需要两个Consumer接口,把两个Consumer接口组合到一起
//源码
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
Consumer<String> con1
Consumer<String> con2
String s = "hello";
con1.accept(s);
con2.accept(s);
//谁先前面,谁先消费
con1.andThen(con2).accept(s);
psvm{
method("a",()->{},()->{})
}
6.常用函数式接口3 Predicate接口
【1】位置:java.util.function.Predicate< T >
【2】方法:
//对某种数据类型的数据进行判断,结果返回布尔值
boolean test(T t)
【3】使用:
//定义一个方法,方法的参数传递String类型字符串,传递一个Predicate接口,泛型使用String,用test判断,返回判断结果
public static boolean check(String s,Predicate<String> pre){
return pre.test(s);
}
psvm {
boolean b = check("abcdef",(String str)->{
return str.length() > 5;
});
}
【4】默认方法1 and:
表示并且关系,也可以用于连接两个判断条件
//源码
//方法内部的两个判断条件,也是用&&运算符连接的
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
public static boolean check(String
s,Predicate<String> pre1, Predicate<String> pre2){
return pre1.and(pre2).test(s);
}
psvm {
boolean b = check("abcdef",
(String str)->{
return str.length() > 5;
},
(String str)->{
return str.contains("a");
});
}
【5】默认方法2 or:
表示或者关系,也可以用于连接两个判断条件
//源码
//方法内部的两个判断条件,也是用||运算符连接的
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
public static boolean check(String
s,Predicate<String> pre1, Predicate<String> pre2){
return pre1.or(pre2).test(s);
}
psvm {
boolean b = check("abcdef",
(String str)->{
return str.length() > 5;
},
(String str)->{
return str.contains("a");
});
}
【6】默认方法3 negate:
表示非关系,取反
//源码
//方法内部的两个判断条件,也是用&&运算符连接的
default Predicate<T> negate() {
return (t) ‐> !test(t);
}
public static boolean check(String
s,Predicate<String> pre){
return pre1.negate().test(s);
}
psvm {
boolean b = check("abcdef",
(String str)->{
return str.length() > 5;
});
}
7.常用函数式接口4 Function接口
【1】位置:java.util.function.Function< T,R >
【2】方法:
//根据一个类型的数据得到另一个类型的数据
R apply(T t)
【3】使用:
//定义一个方法,方法的参数传递一个字符串类型的整数和Function接口,泛型使用String,可以使用apply方法把字符串类型整数转换为Integer类型
public static void change(String s, Function<String,Integer> fun){
//自动拆箱 Integer->Int
Int in = fun.apply(s);
sout(in);
}
psvm {
change("100",(String str)->{
return Integer.parseInt(str);
})
}
【5】默认方法 andThen:
//源码
default <V> Function<T, V> andThen( Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
String ss = fun1.andThen(fun2).apply(s);
sout(ss);
}
psvm {
change("100",
(String str)->{
return Integer.parseInt(str)+10;
},
(String str)->{
return i+"";
});
}
30Stream流
和I/O流完全不同
1.遍历集合
专注于做什么,而不是怎么做
list.stream()
.filter(name->name.startsWith("a"))
.filter(name->name.length()<2)
.forEach(name->System.out.println(name));
2.流式思想的概述
【1】拼接流式模型:建立一个生产线,按照生产线来生产商品
【2】这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性
【3】元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算
【4】数据源流的来源。 可以是集合,数组等
【5】stream操作的两个特征:
(1)pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)
(2)内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法
【6】三个基本步骤:获取一个数据源(source)->数据转换(转换成Stream流)->执行操作获取想要的结果
3.获取流
【1】java.util.stream.Stream< T >(不是函数式接口)
【2】方法1:
//所有Collection集合都有一个默认方法stream,用其获取
default Stream<E> stream()
【3】方法2:
//Stream接口的静态方法of获取数组对应的流
//参数是一个可变参数,所以可以传递一个数组
static <T> Stream<T> of (T... values)
4.流中常用方法
【1】延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用(除了终结方法外,其余方 法均为延迟方法)
【2】终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法
5.常用方法1 forEach
【1】方法签名
void forEach(Consumer<? super T> action);
【2】该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理
【3】基本使用
stream.forEach(name‐> System.out.println(name));
6.常用方法2 filter
【1】方法签名
Stream<T> filter(Predicate<? super T> predicate);
【2】该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件
【3】基本使用
Stream<String> result=original.filter(s->s.startsWith("张"));
7.特点
【1】Stream流是管道流,只能被消费一次
【2】第一个Stream流调用完毕方法,数据就会流转到下一个流上
【3】这时第一个流就会关闭
【4】所以第一个流不能再调用方法了
8.常用方法3 map
【1】方法签名
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
【2】该接口需要一个 Function 函数式接口参数,可以将T类型数据转换成R类型的流
【3】基本使用
Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
9.常用方法4 count
【1】方法签名
long count();
【2】正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来统计Stream中元素个数
【3】终结方法,不能再调用Stream流中的其他方法
【4】基本使用
System.out.println(result.count()); // 2
10.常用方法5 limit
【1】方法签名
Stream<T> limit(long maxSize);
【2】limit 方法可以对流进行截取,只取用前n个
【3】延迟方法,只是对流中的元素截取,返回新的流,可以继续调用流中其他方法
【4】基本使用
Stream<String> result = original.limit(2);
11.常用方法6 skip
【1】方法签名
Stream<T> skip(long n);
【2】如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流
【3】如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
【4】基本使用
Stream<String> result = original.skip(2);
12.常用方法7 concat
【1】方法签名
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
【2】如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
【3】基本使用
Stream<String> result = Stream.concat(streamA, streamB);
31方法引用
1.基本介绍
【1】目的:优化lambda表达式
【2】例子:
public class Demo01PrintSimple {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(s ‐> System.out.println(s));
}
}
【3】具体操作方案:拿到 String(类型可推导,所以可省略)数据后,在控制台中输出它
【4】System.out 对象已经存在, println(String)方法已经存在
【5】方法引用的优化:
让System.out方法直接引用println(String)方法
printString(System.out::println);
【6】双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用
Tips:
Lambda 中传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常
2.通过对象名来引用成员方法
【1】使用前提:
对象名和成员方法都存在
【2】例子:
public class Demo04MethodRef {
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase);
}
}
3.通过类名来引用静态成员方法
【1】使用前提:
类和静态成员方法都存在
【2】例子:
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
4.通过super引用父类的成员方法
【1】父类
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
【2】子类1
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,使用Lambda表达式
method(()‐>{
//创建Human对象,调用sayHello方法
new Human().sayHello();
});
//简化Lambda
method(()‐>new Human().sayHello());
//使用super关键字代替父类对象
method(()‐>super.sayHello());
}
}
【3】子类2(方法引用)
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
}
5.通过this引用本类的成员方法
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
6.类的构造器(构造方法)引用
public class Demo10ConstructorRef {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("赵丽颖", Person::new);
}
}
7.数组的构造器引用
public class Demo12ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, int[]::new);
}
}
32Junit单元测试
1.概述
【1】黑盒测试:不需要写代码,给输入值,看程序是否能输出期望的值
【2】白盒测试:需要写代码,关注具体的执行流程
【3】Junit单元测试:白盒测试的一种
2.使用步骤
【1】定义一个测试类(测试用例)
建议:
- 测试类名:被测试的类名+Test
- 包名:xxxx.xxx.xxx.test
【2】定义测试方法:可以独立运行
建议:
- 方法名:test+测试方法名
- 返回值:void
- 参数列表:空参
【3】给这个方法加注解@Test
【4】导入Junit依赖环境
【5】判定结果:
- 红色:失败
- 绿色:成功
- 一般用断言处理
Assert.assertEquals(期望的结果, 预测的结果);
3. @Before & @After
【1】@Before:所有测试方法在执行之前都会先执行该方法
【2】@After :在所有测试方法执行完后都会自动执行该方法
33反射
1.概述:框架设计的灵魂
【1】框架:半成品软件,可以在框架基础上进行软件开发,简化编码
【2】反射:将类的各个部分封装为其他对象,这就是反射机制
【3】好处:
(1)在程序的运行过程中,操作这些对象
(2)可以解耦,来提高程序的可扩展性
2.获取Class对象的三种方式
【1】Source源代码阶段:Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象(多用于配置文件,将类名定义在配置文件中,读取文件,加载类)
【2】Class类对象阶段:类名.class:通过类的属性class来获取(多用于参数的传递)
【3】Runtime运行时阶段:对象.getClass():getClass()方法在Object类中定义(多用于对象的获取字节码的方式)
【4】结论:同一个字节码文件(*.class),在一次的程序运行中,只会被加载一次,不论通过哪个方法获取的Class对象都是同一个
3.使用Class对象概述
获取功能:
【1】获取成员变量们
【2】获取构造方法们
【3】获取成员方法们
【4】获取类名
String getName()
4.Class对象功能 获取Field
【1】获取方法:
//获取所有public修饰的成员变量
Field[] getFields()
//获取指定名称的public修饰的成员变量
Field getField(String name )
//获取所有的成员变量,不考虑修饰符
Field[] getDeclaredFields()
Field getDeclaredField(String name)
【2】操作1:设置值
//参数:obj:对象名
// value:设置的值
void set(Object obj, Object value)
【3】操作2:获取值
//参数:obj:对象名
get(Object obj)
【4】访问私有变量:忽略访问权限修饰符的安全检查
//暴力反射
setAccessible(true);
5.Class对象功能 获取Constructor
【1】获取方法:
Constructor<?>[] getConstructors()
//参数:变量类型.class
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
【2】创建对象:
//参数:initargs:对象需要的参数
T newInstance(Object... initargs)
【3】如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
6.Class对象功能 获取Method
【1】获取方法:
//父类方法(Object类方法)也存在其中
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
【2】执行方法:
//参数:obj:对象名
// args:方法的参数
Object invoke(Object obj, Object... args)
【3】获取方法名称:
String getName:获取方法名
7.使用反射
【1】加载配置文件
//创建Properties对象
Properties pro = new Properties();
//获取class目录下的配置文件
ClassLoader cl = 类名称.class.getClassLoader();
InputStream is = cl.getResourceAsStream("pro.properties")
//加载配置文件,转换为一个集合
pro.load(is);
【2】获取配置文件中定义的数据
String className = pro.getProperty("className")
String methodName = pro.getProperty("methodName")
【3】加载该类进内存
Class cls = Class.forName(className);
【4】创建对象
//空参构造方法
Object obj = cls.newInstance();
【5】获取方法对象
Method method = cls.getMethod(methodName);
【6】执行方法
method.invoke(obj);
34注解
1.概念
【1】注释:用文字描述程序,给程序员看
【2】注解:说明程序,给计算机看
【3】概念描述:
- JDK1.5之后的新特性
- 说明程序的
- 使用注解:@注解名称
【4】作用分类:
(1)编写文档:通过代码里标识的注解生成文档(用javadoc生成文档doc文档)
(2)代码分析:通过代码里标识的注解对代码进行分析(使用反射)
(3)编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(Override)
2.JDK中约定的一些注解
【1】@Override:用来检测被该注解标注的方法是否是继承自父类的
【2】@Deprecated:将该注解标注的内容,表示已过时(调用方法后,方法名会有横线被划去)
【3】@SuppressWarnings:压制警告,一般@SuppressWarnings(“all”),让编译器不发出警告
3.自定义注解
【1】格式:
元注解
public @interface 注解名称{}
【2】本质:注解本质上就是一个接口,该接口默认继承Annotation接口
public interface MyAnno extends java.lang.annotation.Annotation {}
【3】属性:接口中可以定义的成员方法
4. 属性定义
【1】属性:接口中的抽象方法
【2】要求:
(1)属性的返回值类型有下列取值:基本数据类型,String,枚举,注解,以上类型的数组
(2)定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
5. 元注解
【1】用于描述注解的注解
【2】@Target:描述注解能够作用的位置
- ElementType取值:
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
//表示这个注解只能作用在类上
//ElementType是一个枚举
@Target(value = {ElementType.TYPE})
【3】@Retention:描述注解被保留的阶段
- SOURCE
- CLASS
- RUNTIME
//当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
//一般都取RUNTIME
@Retention(RetentionPolicy.RUNTIME)
【4】@Documented:描述注解是否被抽取到api文档中
【5】@Inherited:描述注解是否被子类继承
6.解析注解
【1】在程序使用(解析)注解:获取注解中定义的属性值
【2】反射中让注解来做配置文件的工作
【3】步骤
(1)获取注解定义的位置的对象 (Class,Method,Field)
(2)获取指定的注解
(3)调用注解中的抽象方法获取配置的属性值
//描述需要去执行的类名和方法名
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @Interface Pro {
String className();
String methodName();
}
//框架类
@Pro(className = "",methodName = "")
public class xxx{
psvm{
//1.解析注解
//获取该类的字节码文件对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;
//2.获取上边的注解对象
//其实就是在内存中生成了一个该注解接口的子类实现对象
Pro an = reflectTestClass.getAnnotation(Pro.class);
//3.调用注解对象中定义的抽象方法/属性,获取返回值
String className = an.className();
String methodName = an.methodName();
//和创建配置一样 从加载该类进内存开始
Class cls = Class.forName(className);
Object obj = cls.newInstance();
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}