java.util.Calendar类:日历类
1.Calendar 类是一个抽象类,里面提供了很多操作日历字段的方法
2.Calendar 类无法直接创建对象使用,里面有一个静态方法叫getInstance(),该方法返回了Calender 类的子类对象
3.static Calendar getInstance()使用默认时区和语言环境获得一个日历
public class DemoCalendar(){
public satice void main(String[] args){
Calendar c = Calendar.getInstance();//多态
Systen.out.println(c);//输出一个日历,里面包括年月日时分秒等等,通过其成员方法可以修改或添加日历信息
}
}
calendar类的常用成员方法
- public int get(int field):返回给定日历字段的值。
- public void set(int fleid, int value):将给定的日历字段设置为给定值。
- public abstract void add(int field ,int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量
- public Date getTime():返回一个表示此Calender时间值的Date对象,把一个日历对象转化为日期对象
- int field:日历字段(日历中的相关信息):YEAR:年,MONTH:月…
下面展示一些内联代码片
。
public class DemoCalender {
public static void main(String[] args) {
demo01();
}
public static void demo01() {
//public int get(int field):返回给定日历字段的值。
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
System.out.println(year);
int month = c.get(Calendar.MONTH);
System.out.println(month);
int day = c.get(Calendar.DAY_OF_MONTH);
System.out.println(day);
// public void set(int fleid, int value):将给定的日历字段设置为给定值。
System.out.println("=================================");
c.set(Calendar.MONTH,3);
int month2 = c.get(Calendar.MONTH);
System.out.println(month2);
//public abstract void add(int field ,int amount):根据日历的规则,
// 为给定的日历字段添加或减去指定的时间量
System.out.println("=================================");
c.add(Calendar.MONTH,-1);
int month3 = c.get(Calendar.MONTH);
System.out.println(month3);
//public Date getTime():返回一个表示此Calender时间值的Date对象
Date date = c.getTime();
System.out.println(date);
}
}
System类的常用方法
- public static long currentTimeMillis():返回以毫秒为单位的当前时间
- public static void arraycopy():将数组中指定的数据拷贝到另一个数组。
public class Demo01System {
public static void main(String[] args) {
// demo01();
demo02();
}
private static void demo02() {
//public static void arraycopy():将数组中指定的数据拷贝到另一个数组。
//定义源数组
int [] src = {1,2,3,4,5};
//定义目标数组
int[] dest = {6,7,8,9,10};
System.out.println("复制前:"+Arrays.toString(dest));
System.arraycopy(src,0,dest,0,3);//src:源数组,dest:目标数组,3是元素个数,两个0是数组复制起始目标索引。
System.out.println("复制后:"+Arrays.toString(dest));
/*
复制前:[6, 7, 8, 9, 10]
复制后:[1, 2, 3, 9, 10]
数组长度在java中无法改变
*/
}
private static void demo01(){
//public static long currentTimeMillis():返回以毫秒为单位的当前时间,
// 一般用来验证程序的效率
long s = System.currentTimeMillis();
for (int i = 0; i < 9999; i++) {
System.out.print(i);
}
System.out.println("");
long e = System.currentTimeMillis();
System.out.println("程序共耗时:"+(e -s) +"毫秒");
}
}
字符串缓冲区StringBuilder方法
字符串是常量,底层是一个被final修饰的数组,创建之后不能改变。
进行字符串的相加,其实是新建一个新的字符串,把原来的字符串名贴在新字符串的地址上。
StringBuilder类
字符串缓冲区,底层也是一个数组,但没有被final修饰,可以改变长度。
常用构造方法:
- public StringBuilder append():添加任意类型数据的字符串形式,并返回当前对象自身
- toString():将当前StringBuilder对象转换为String对象
String和StringBuilder可以相互转换:
- StringBuilder(String str) str->SB
- public String toString(): SB->str
String str = SB.toString();
public class Demo01StringBuilder {
public static void main(String[] args) {
//空参构造方法
StringBuilder sb1 = new StringBuilder();
System.out.println("sb1:"+sb1);
//带参构造方法
StringBuilder sb2 = new StringBuilder("abc");
System.out.println("sb2:"+sb2);//sb2:abc
//append():添加任意类型数据的字符串形式,并返回当前对象自身
StringBuilder sb3 = new StringBuilder();
sb3.append(81);
//链式编程
sb3.append(80).append(true).append("str").append(8.8);
System.out.println(sb3);
//字符串和字符串缓冲区的转换
demo01();
}
//字符串和字符串缓冲区的转换
public static void demo01(){
String str = "Hello";
System.out.println("str:"+str);
StringBuilder SB = new StringBuilder(str);
SB.append(" World");
String str2 = SB.toString();
System.out.println("str2:"+str2);
}
}
第五章:包装类
概念
基本数据类型,使用起来非常方便,但是没有对应的方法来操作这些基本类型的数据
可以使用一个类,把基本数据类型的数据装起来,在类中定义一些方法,这个类叫做包装类 我们可
以使用类中的方法来操作这些基本类型的数据
int-> Integer
char -> Character
byte,short,long,float,double,boolean这些都是首字母大写
装箱与拆箱
包装类和基本数据类型之间可以自动转换
public class DemoPakeage {
public static void main(String[] args) {
/*
自动装箱
相当于Integer in = new Integer(1);
*/
Integer in = 1;//相当于Integer in = new Integer(1);
/*
自动拆箱
相当于in = in.intValue()+2 = 3;
*/
in= in+2;
/*
ArrayList集合无法直接存储整数,但是可以存储包装类
*/
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
int a = list.get(0);//list.get(0).intValue();
}
基本类型与字符串之间的转换
- 基本类型的值+""最简单的方法(工作中常用)
- 包装类的静态方法toString(参数),
static String toString(int i),返回一个指定的整数的String对象 - String类的静态方法VauleOf(int i)返回int参数的字符串表示形式
- 使用包装类的静态方法parseXXX(“字符串”)
Integer类:static int parseInt(String s)
Double类:static double parseDouble(String s)
public class Demo03Integer {
public static void main(String[] args) {
//基本类型->字符串
int i1 = 100;
String s1 = i1 + "";//这里的100是字符串不是数字
System.out.println(s1+200);//100200字符串相加
String s2 = Integer.toString(100);//100
System.out.println(s2+100);//100100
String s3 = String.valueOf(i1);//100
System.out.println(s3);/100
//字符串->基本类型
int i = Integer.parseInt(s1);
System.out.println(i-10); //90
}
}
Collection集合
java.util.Collection接口
说有单列集合的最顶层的接口,里面定义了所有单列集合共性的方法
任意的单列集合都可以使用Collection接口中的方法
Collection 常用的方法(即单列集合共性的方法)
1. public void clear():清空集合
2. public boolean remove(E e):把e从数组中移除
3. public boolean contains(E e):判断数组中是否有e
4. public boolean isEmpty():判断数组是否为空
5. public int size():返回集合中元素的个数
6. public Object[] toArray():把集合中的元素,存储到数组中
7. public boolean add(E e): 把给定的对象添加到当前集合中
public class DemoCollection {
public static void main(String[] args) {
//创建集合对象,可以使用多态
Collection<String> coll = new ArrayList<>();
System.out.println(coll);
//public boolean add(E e): 把给定的对象添加到当前集合中
boolean b1 = coll.add("张三");
System.out.println(b1);
System.out.println(coll);
coll.add("李四");
coll.add("王五");
coll.add("赵六");
coll.add("田七");
System.out.println(coll);
//public boolean remove(E e):把e从E数组中移除
System.out.println("============================");
boolean b2 =coll.remove("赵六");
System.out.println(coll);
//public boolean contains(E e):判断数组中是否有e
System.out.println("============================");
boolean b3 =coll.contains("田七");
System.out.println(b3);
//public boolean isEmpty():判断数组是否为空
System.out.println("============================");
boolean b4 = coll.isEmpty();
System.out.println(b4);
//public int size():返回集合中元素的个数
System.out.println("============================");
int b5 = coll.size();
System.out.println(b5);
//public Object[] toArray():把集合中的元素,存储到数组中
System.out.println("============================");
Object[] obj = coll.toArray();
for (int i = 0; i < obj.length; i++) {
System.out.println(obj[i]);
}
}
}
java泛型
泛型:让数据类型变得参数化,可以看做是未知的数据类型,相当于未知变量(指数据类型),可以用来接收数据
E e element 元素
T t Type 类型
定义泛型时,对应的数据类型是不确定的。泛型方法被调用时,会指定具体类型。
核心目标:解决容器类型在编译时安全检查的问题。就是用于类型检查
优点:
- 避免了类型转换,存储的是什么类型,取出的就是什么类型。
- 如果想要存储的数据的数据类型出错,会直接报错,把运行期错误(程序运行)提前为编译期错误(写程序的时候)
定义泛型类
//当我们不确定数据类型的时候用泛型代替
public class ArrayList<E>{
public boolean add(E e){}
public E get(int index){}
}
//当我们传递数据时,自动把E替换成想要的类型,这里替换成了String
ArrayList<String> list = new ArrayList<>();
含有泛型的方法
public <E> void GenericMethod(E e){
System.out.println(e);
}
静态方法可以类名.方法名直接使用,不需要new个实例对象
//含有泛型的接口
迭代器Iterator(集合都可以使用迭代器)
java.util.Iterator接口:迭代器(可用于对集合遍历,有点像递归)
常用方法:
-
boolean hasNext() 判断集合中有没有下一个元素,有就返回true
-
E next() 返回迭代的下一个元素,取出集合中的下一个元素
【注】:Iterator迭代器,是一个接口,无法直接使用,需要使用Iterator接口的实现类对象,
获取实现类对象的方法比较特殊,要用Collection接口中的一个方法iterator(),
该方法返回的就是迭代器的实现类对象
Iterator iterator() 返回在此Collection的元素上进行迭代的迭代器迭代器使用步骤:
- 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
- 使用Iterator接口中的hasNext()方法,判断是否有下一个元素
- 使用Iteratot接口中的next()方法,取出集合的下一个元素
public class demo02Iterator {
public static void main(String[] args) {
//创建一个集合
Collection<String> coll = new ArrayList<>();//这里使用了多态的写法,前面是接口,后面是实现类
//添加元素
coll.add("博丽灵梦");
coll.add("雾雨魔理沙");
coll.add("十六夜咲夜");
coll.add("东风谷早苗");
coll.add("伊吹萃香");
//获取迭代器,并把指针(索引)指向集合的-1位置
//Iterator<E> iterator() 返回在此Collection的元素上进行迭代的迭代器
Iterator<String> it = coll.iterator();
//使用循环遍历集合
//boolean hasNext() 判断集合中有没有下一个元素,有就返回true
//E next() 返回迭代的下一个元素,取出集合中的下一个元素
while(it.hasNext()){
System.out.println(it.next());//指针下移至0位置,并返回元素
}
}
}
增强for循环
增强for循环:底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写
Collection<E>extends Iterable<E>:所有的单列集合都乐意使用增强for
public interface Iterable<E>:这个接口的实现类允许成为“foreach”语言的目标
作用:用来遍历集合和数组
格式:
for(集合/数组的数据类型 变量名:集合名/数组名){
sout(变量名);
}
import java.util.ArrayList;
public class Demo04for {
public static void main(String[] args) {
demo01();
demo02();
}
private static void demo01(){
int [] array = {1,2,3,4,5};
for(int i: array){
System.out.println(i);
}
}
private static void demo02(){
ArrayList<String> list = new ArrayList<>();
list.add("博丽灵梦");
list.add("雾雨魔理沙");
list.add("十六夜咲夜");
list.add("东风谷早苗");
list.add("芙兰朵露");
for(String i: list){
System.out.println(i);
}
}
}
泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型的类型不确定,可以通过通配符<?>表示,但是一旦适应泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法(以及继承其父类Collection的方法)无法使用。
泛型的通配符:
?:代表任意的数据类型
注意:1.创建对象时不能使用
2.只能作为方法传递集合的参数使用
import java.util.ArrayList;
import java.util.Iterator;
public class Demo05 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
ArrayList<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");
printArray(list1);
printArray(list2);
}
//在集合定义的时候不能用泛型通配符,但是在集合作为参数被传递的时候可以使用
public static void printArray(ArrayList<?> list){//这里使用了泛型通配符,list只能接受数据,不能存储数据
Iterator<?> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
- 泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
- 泛型的下限限定:? extends E 代表使用的泛型只能是E类型的父类/本身
import java.util.ArrayList;
import java.util.Collection;
public class Demo06Generic {
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);//Integer是Number的子类
getElement1(list2);
getElement1(list3);//本身
getElement1(list4);
getElement2(list1);
getElement2(list2);
getElement2(list3);//本身
getElement2(list4);//父类
}
//泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
public static void getElement1(Collection<? extends Number> coll){}
//泛型的下限限定:? extends E 代表使用的泛型只能是E类型的父类/本身
public static void getElement2(Collection<? super Number> coll){}
}
数据结构
数组
- 查询快:根据索引就可以找到指定元素
- 增删慢:需创建新的数组,把原数组未增删的部分复制过来,把新地址赋值回去
List集合
介绍及常用方法
list接口的特点:
1.有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出123)
2.有索引,包含了一些带索引的方法
3.允许存储重复的元素
List接口带索引的方法(特有)
1.add(int index, E element) 将指定的元素到这个列表的末尾(可选操作)。
2.get(int index) 返回此列表中指定位置的元素.
3.remove(int index) 移除此列表中指定位置的元素(可选操作)。
4.set(int index, E element) 用指定元素替换此列表中指定位置的元素(可选操作)。
public class DemoList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();//多态的写法
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");//可以存储相同的元素
System.out.println("=======================");
//1.add(int index, E element) 将指定的元素到这个列表的末尾(可选操作)。
list.add(3,"ithema");
System.out.println(list);
System.out.println("============================");
//2.get(int index) 返回此列表中指定位置的元素.
String s = list.get(3);
System.out.println(s);
System.out.println("============================");
//3.remove(int index) 移除此列表中指定位置的元素(可选操作)。
String b = list.remove(3);
System.out.println(b);
System.out.println("===========================");
//4.set(int index, E element) 用指定元素替换此列表中指定位置的元素(可选操作)。
String a = list.set(4, "A");
System.out.println(a);
System.out.println(list);
}
}
LinkedList集合的特点(需要操作链表首尾的时候使用)
LinkedList接口的特点:
1.顶层是一个链表结构:查询慢,增删快
2.里面包含了大量操作首尾元素的方法
注意:使用使用LinkList特有的方法不能使用多态
特有方法:
- addFirst(E e) 在该列表开头插入指定的元素。
- addLast(E e) 将指定的元素追加到此列表的末尾。
- getFirst() 返回此列表中的第一个元素。
- getLast() 返回此列表中的最后一个元素
- push(E e) 将元素推送到由此列表表示的堆栈上。相当于addFirst(E e)
- removeFirst() 从此列表中删除并返回第一个元素。
- removeLast() 从此列表中删除并返回最后一个元素。
- pop() 从此列表表示的堆栈中弹出一个元素。
数据结构
链表
哈希值
哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的,不是数据实际存储的物理地址)
int hashCode() 返回该对象的哈希码值
set接口(查询速度快,但是不能存储重复元素)
java.util.Set接口 extends Collection 接口
Set接口的特点:
- 不允许存储重复的元素
- 没有索引,也没有带索引的方法,也不能使用普通for循环遍历
- 是一个无序的集合,存储元素和取出元素的顺序有可能不一致
- 底层是一个哈希表结构(查询的速度非常的快)
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo01Set {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
HashSet集合存储自定义类型元素
set集合存储的元素(String,Integer,…Student,Person…)唯一
要求:同名同年龄的人,视为同一个人,只能存储一次(Hash值相同,equals方法判断相同的元素唯一)
public class Demo03HashSetSavePerson {
public static void main(String[] args) {
//创建HashSet集合存储Person
HashSet<Person> set =new HashSet<>();
Person p1 = new Person("小美女",18);
Person p2 = new Person("小美女",18);
Person p3 = new Person("小美女",19);
System.out.println(p1.hashCode());//
System.out.println(p2.hashCode());
System.out.println(p3.hashCode());
System.out.println(p1==p2);//false(==比较的是地址值),没有重写Hashset and insert方法,比较的是三者地址值
System.out.println(p1.equals(p2));//true(equals方法比较的是实际内容),重写了HI方法,比较的是Hash值
set.add(p1);//没有重写HI方法,三者地址不同,三者都可以存储经set集合
set.add(p2);//重写方法后p1==p2,两者hash值一致,内容相同,因为不可存储相同元素,只能存储其中之一
set.add(p3);
System.out.println(set);
}
}
【注释】:
- ==比较的是地址值
- equals方法比较的是实际内容
LinkedHashSet(HashSet的子类,有序的自定义类型存储)
public class Demo04LinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("www");
set.add("abc");
set.add("abc");
set.add("itcast");
System.out.println(set);//[abc, www, itcast]无序,按照ASIC码排序,不允许重复
LinkedHashSet<String> linked = new LinkedHashSet<>();
linked.add("www");
linked.add("abc");
linked.add("abc");
linked.add("itcast");
System.out.println(linked);//[www, abc, itcast] 有序,什么顺序存储,就是什么顺序输出,不允许重复
}
}
可变参数(当不确定参数个数的时候使用,实际是数组)
使用情况:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数,可变参数实际是一个数组
- 格式:
修饰符 返回值类型 方法名(数据类型…变量名){} - 原理:
可变参数的底层是一个数组,根据传递参数个数的不同,会创建不同长度的数组,来存储这些参数
public class Demo01VarArgs {
public static void main(String[] args) {
int i=add(10,20);
System.out.println(i);
}
/*
已知:计算整数的和,数据类型确定,元素个数不定,
因为:可变参数int...arr的底层实际是一个数组
所以:add()建立时,就会创建一个长度为0的数组,存储传递过来的参数 new int[]{10,20};
*/
public static int add(int...arr){
System.out.println(arr);
System.out.println(arr.length);
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
}
可变参数(实际是个数组,当不确定传递多少个参数时使用)
使用情况:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数,可变参数实际是一个数组
格式:
修饰符 返回值类型 方法名(数据类型...变量名){}
原理:
可变参数的底层是一个数组,根据传递参数个数的不同,会创建不同长度的数组,来存储这些参数
快捷键目录标题文本样式列表链接代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图插入类图
代码片复制
下面展示一些 内联代码片
。
public class Demo01VarArgs {
public static void main(String[] args) {
int i=add(10,20);
System.out.println(i);
}
public static int add(int...arr){
System.out.println(arr);
System.out.println(arr.length);
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
已知:计算整数的和,数据类型确定,元素个数不定,
因为:可变参数int...arr的底层实际是一个数组
所以:add()建立时,就会创建一个长度为0的数组,存储传递过来的参数 new int[]{10,20};
可变参数的注意事项:
1. 一个方法的参数列表,只能有一个可变参数
2. 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
3. Object…obj ,Object数据类型作为可变参数,功能强大,可以接受任意类型数据
Collections集合工具类的常用方法
【注意】Colllection接口是集合层次结构中的根界面
Collections是集合工具类,用来对集合进行操作,
常用方法如下:
- addAll(Collection<? super T> c, T… elements) 将所有指定的元素添加到指定的集合。
- shuffle(List<?> list) 打乱列表。
- sort(List list) 根据其元素的natural ordering对指定的列表进行排序
- sort(List list, Comparator<? super T> c) 传递集合和比较规则
根据指定的比较器引起的顺序对指定的列表进行排序。
comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
comparator:相当于找一个第三方的裁判,比较两个 - Comparator的排序规则:
o1-o2:升序,
o2-o1:降序
public class Demo02Sort {
public static void main(String[] args) {
//加入多个元素到集合
ArrayList<Integer> list01 = new ArrayList<>();
Collections.addAll(list01, 1, 2, 3);
System.out.println(list01);//[1, 2, 3]
//打乱集合元素
Collections.shuffle(list01);
System.out.println(list01);//[2, 3, 1]
//升序排序
ArrayList<String> list02 = new ArrayList<>();
Collections.addAll(list02,"a","c","b");
System.out.println(list02);//[a, c, b]
Collections.sort(list02);
System.out.println(list02);//[a, b, c]
//自定义排序
ArrayList<Person> list03 = new ArrayList<>();
list03.add(new Person("张三",18));
list03.add(new Person("李四",19));
list03.add(new Person("王五",17));
System.out.println(list03);
//重写了Person类的CompareTo方法,按照年龄大小排序
Collections.sort(list03);
System.out.println(list03);//[Person{name='王五', age=17}, Person{name='张三', age=18}, Person{name='李四', age=19}]
}
}
如果是排序自己写的实现类,就必须重写了该实现类的CompareTo方法
@Override
public int compareTo(Person o) {
//return 0;//认为元素都是相同的
//自定义比较规则,比较两个人的年龄(this,参数Person)
//return this.getAge()-o.getAge();//升序排序
return o.getAge()-this.getAge();//降序排序
this(自己)-o(参数):升序
o(参数)-this(自己):降序
Map集合
util.Map<K,V>集合
Map集合的特点:
- Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
- Map集合中的元素,key和value的数据类型是可以相同,也可以不同
- key不允许重复,但是value是可以重复的
- key和value是一一对应的
Map集合的常用实现类:
HashMap<k,v>集合的特点:
5. HashMap的底层是哈希表:查询速度非常快
6. HashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致
LinkedHashMap<k,v>集合的特点:
- 低层是哈希表+链表(保证迭代的顺序)
- 是一个有序的集合,存储元素和取出元素的顺序是一致的
输出集合map,若输出的不是地址值,则该方法重写了toString方法
Map常用方法:
8. put(K key, V value)将指定的key和指定的value添加到Map当中。
若key重复着return null,若key不重复则替换成新value并return 被替换的value
9. remove(Object key) 移除指定key的value并返回 。return v
10. get(Object key) 获取指定key的value,若无这返回null。return v
11. containsKey(Object key) 是否包含指定的key,则返回 true 。return boolean
12. keySet():返回一个set集合,可通过遍历set集合间接遍历Map集合
public class Demo03Map {
public static void main(String[] args) {
show01();
show02();
show03();
}
private static void show03() {
Map<String,Integer> map03 = new HashMap<>();//尽量使用int等常用数据类型的包装类
map03.put("赵丽颖",168);
map03.put("杨颖",165);
map03.put("林志玲",178);
Integer v1 = map03.get("杨颖");
System.out.println("v1:"+v1);//v1:165
Integer v2 =map03.get("迪丽热巴");
System.out.println("v2:"+v2);//v2:null
}
private static void show02() {
Map<String,Integer> map02 = new HashMap<>();//多态写法
map02.put("a",1);
map02.put("b",2);
map02.put("c",3);
Integer s = map02.remove("a");
System.out.println(s);//1
System.out.println(map02);//{b=2, c=3}
}
private static void show01(){
Map<String,String> map = new HashMap<>();
String v1 = map.put("李晨", "范冰冰1");
System.out.println(v1);
String v2 = map.put("李晨", "范冰冰2");
System.out.println(v2);
map.put("1","a");
map.put("2","b");
map.put("3","c");
System.out.println(map);//输出的不是地址值,说明map重写了toString方法
}
}
Map集合的常用遍历方法
第一种遍历方法:键找值
Set<K> keySet()返回对应的set集合
步骤:
1.Map集合的方法keySet()方法获取set()集合
2.遍历set()集合获取每一个key值:迭代器,增强for循环
3.通过Map集合的方法get(key)获取对应key的value
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Demo02KeySet {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("赵丽颖",168);
map.put("杨颖",165);
map.put("林志玲",165);
Set<String> s = map.keySet();//获取map中key的set集合
System.out.println("--------------------------");//迭代器
Iterator<String> it = s.iterator();
while(it.hasNext()){
System.out.println(map.get(it.next()));
}
System.out.println("--------------------------");//增强for循环
for(String key:s){
System.out.println(map.get(key));
}
}
}
Map集合遍历的第二种方式:使用Entry对象遍历
Map集合中的方法:
set<Map.Entry<K,V>> entrySet() 返回此映像中包含的映射关系的Set视图。(返回一个set集合)
步骤:1.使用Map集合中的EntrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中,
2.遍历Set()集合,获取每一个Entry对象
3.使用Entry()中的getKey()和getValue()方法获取键值对
public class Demo04Map2 {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("赵丽颖",168);
map.put("杨颖",165);
map.put("林志玲",178);
Set<Map.Entry<String,Integer>> set = map.entrySet();//获取map的entry(键值对)的set集合
System.out.println("--------------------------");
Iterator<Map.Entry<String,Integer>> it = set.iterator();
while (it.hasNext()){
Map.Entry<String,Integer> entry = it.next();
String key = entry.getKey();
Integer value =entry.getValue();
System.out.println(key+ "=" + value);
}
System.out.println("--------------------------");
for (Map.Entry<String, Integer> me : set) {
String key =me.getKey();
Integer value = me.getValue();
System.out.println(key+"="+value);
}
}
Map集合存储自定义类型(存储键值对,key值唯一)
HashMap集合存储自定义键值
Map集合保证key是唯一的:必须重写hashCode方法和equals方法,以保证key唯一
若key值重复,则会用新的value值替换旧的value值。
public class HashMapSavePerson {
public static void main(String[] args) {
show01();
}
private static void show01() {
HashMap<String,Person> map = new HashMap<>();
//key重复,会用新的value替换旧的value。
map.put("北京",new Person("张三",18));
map.put("上海",new Person("李四",19));
map.put("广州",new Person("王五",20));
map.put("北京",new Person("赵六",18));
Set<String> set = map.keySet(); //获取map关于key的set集合
for (String key : set) {
Person value = map.get(key);
System.out.println(key + "-->" + value);//Person重写了toString方法,所以输出的额不是地址而是内容
/*
上海-->Person{name='李四', age=19}
广州-->Person{name='王五', age=20}
北京-->Person{name='赵六', age=18}
*/
}
}
LinkedHashMap(输入和输出的顺序一致的hashmap集合)
public class Demo01LinkedHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("a","a");
map.put("c","c");
map.put("b","b");
map.put("a","e");
System.out.println(map);//{a=e, b=b, c=c}hashmap集合的顺序按照ASIC码,
//有可能输入和输出顺序不一致
System.out.println("---------------------------");
LinkedHashMap<String,String> linked = new LinkedHashMap();
linked.put("a","a");
linked.put("c","c");
linked.put("d","d");
linked.put("a","e");
System.out.println(linked);//{a=e, c=c, d=d}
// LinkedHashMap集合输入和输出的顺序一致
}
}
计算字符串中每个字符的个数
练习:计算一个字符串中每个字出现的次数
分析:
1.使用Scanner获取用户输入的字符串。
2.创建Map集合,key是字符串中的字符,value是字符串中字符的个数。
3.遍历字符串,获取每一个字符
4.使用获取到的字符,去map集合中判断key是否存在
key存在:
通过map.get(key)的方法获取相应的value;
并value++;
map.put(key,value)方法更新map集合
key不存在:
则map.put(key,1),使value为1
public class Demo03MapTest {
public static void main(String[] args) {
//1.使用Scanner获取用户输入的字符串。
System.out.println("请输入一段字符串:");
Scanner sc = new Scanner(System.in);
String str = sc.next();
//2.创建Map集合,key是字符串中的字符,value是字符串中字符的个数。
Map<Character,Integer> map = new HashMap<>();
for (char c : str.toCharArray()) {
if(map.containsKey(c)){
Integer value =map.get(c);
value++;
map.put(c,value);
}else{
map.put(c,1);
}
}
System.out.println(map);//{a=3, c=2, v=4}
}
}
JDK9版本新增静态方法of
List几口,Set接口,Map接口:里面新增了一个静态方法of,可以给集合一次性添加多个元素
static<E> List<E> of (E...element)
使用前提:
当集合中存储的元素的个数已经确定了,不在改变时使用
注意:
1.of方法只适用于List接口,Set接口,Map接口,不适用于接接口的实现类
2.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常
3.Set接口和Map接口在调用of方法的时候,不能有重复元素,否则会抛出异常
public class Demo01JDK9 {
public static void main(String[] args) {
List<String> list = List.of("a","b","a","c","d",);
System.out.println(list);
Set<String> set = Set.of("a","b","a","c","d",);
System.out.println(set);
}
}
Debug调试程序说明
使用方式:
在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
右键,选择Debug执行程序
程序就会停留在添加的第一个断点处
执行程序:
f8:逐行执行程序
f7:进入到方法
shift+f8:跳出方法
f9:调到下一个断点,如果没有下一个断点,那么久结束程序
ctrl+f2:推出debug模式,停止程序
Console:切换到控制台
斗地主案例
package demo18;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/*
斗地主综合案例:
1.准备牌
2.洗牌
3.发牌
4.排序
5.看牌
*/
public class DouDiZhu {
public static void main(String[] args) {
//1.准备牌
//创建一个Map集合存储牌和牌的索引
HashMap<Integer,String> poker = new HashMap<>();
ArrayList<Integer> pokerindex = new ArrayList<>();
List<String> color = new ArrayList<>();
Collections.addAll(color,"♥","♠","♣","♦");
List<String> number = new ArrayList<>();
Collections.addAll(number,"2","A","K","Q","J","10","9","8","7","6","5","4","3");
int index = 0;
poker.put(index,"大王");
pokerindex.add(index);
index++;
poker.put(index,"小王");
pokerindex.add(index);
index++;
for (String c : number) {
for (String n : color) {
String Poker = c+n;
poker.put(index,Poker);
pokerindex.add(index);
index++;
}
}
System.out.println(poker);
System.out.println(pokerindex);
System.out.println("-------------------");
//洗牌
Collections.shuffle(pokerindex);
System.out.println(pokerindex);
//发牌
ArrayList<Integer> player01 = new ArrayList<>();
ArrayList<Integer> player02 = new ArrayList<>();
ArrayList<Integer> player03 = new ArrayList<>();
ArrayList<Integer> DiPai = new ArrayList<>();
for (int i = 0; i < pokerindex.size(); i++) {
Integer in = pokerindex.get(i);
if(i>=51){
DiPai.add(in);
}else if (i%3==0){
//给玩家一发牌
player01.add(in);
}else if (i%3==1){
//给玩家二发牌
player02.add(in);
}else if (i%3==2){
//给玩家三发牌
player03.add(in);
}
}
/*
4.排序
使用Collections中的方法sort(List)
*/
Collections.sort(DiPai);
Collections.sort(player01);
Collections.sort(player02);
Collections.sort(player03);
/*
5.看牌
定义一个看牌的方法,提高程序的复用性
参数:
String name:玩家名称
HashMap<Integer,String> poker:存储牌的poker集合
存储玩家牌的索引的集合
*/
show("周润发",poker,player01);
show("刘德华",poker,player02);
show("周星驰",poker,player03);
show("底牌",poker,DiPai);
}
public static void show(String name,HashMap<Integer,String> poker,ArrayList<Integer> list){
System.out.print(name+":");
for (Integer key : list) {
String value = poker.get(key);
System.out.print(value+" ");
}
System.out.println();
}
}
异常的解析及其处理方式
java.lang.Throwable类:是java语言中所有错误或异常的超类。
Exception:编译期异常,进行编译(写代码)java程序出现的问题
RuntimeException:运行期异常,java程序运行过程中出现的问题
异常就相当于程序得了一个小毛病(感冒,发烧),把异常处理掉,可以继续运行
解决方式:
1. try...catch
2. ctrl+enter throw(抛出这个异常)
Error错误:
错误相当于程序得了一个无法治愈的绝症,必须修改源代码。
public class Demo01Exception {
public static void main(String[] args) {
int[] arr = {1,2,3};
try{
System.out.println(arr[3]);//可能出现异常的程序,这里是数组越界异常
}catch (Exception e){
System.out.println("后续代码");//后面的代码依旧会执行,throw方法则不会。
}
}
}
java异常处理的五个关键字:try,catch,finally,throw,throws..
throw关键字:
作用:可以使用throw关键字在指定的方法中抛出指定的异常
使用格式:
throw new xxxException("异常产生的原因");
注意:
1.throw关键字必须写在方法的内部
2.throw关键字后边new的对象必须是Exception或者Exception的子类对象
3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws(抛出异常),要么try...catch
public class Demo01ExceptionThrow {
public static void main(String[] args) {
int[] arr = new int[3];
int e = getElement(arr,3);
System.out.println(e);
}
private static int getElement(int[] arr, int index) {
/*
我们对传递过来的参数数组,进行合法性校验
如果数组arr的值是空
则抛出空指针异常
*/
//NullPointerException是一个运行期异常,我们不用处理,默认交给JVM处理。
if(arr == null){
throw new NullPointerException("传递的数组的值是空");
}
//索引越界异常
if(index<0||index>arr.length)
{
throw new ArrayIndexOutOfBoundsException("传递的数组的索引超出数组的使用范围");
}
int ele = arr[index];
return ele;
}
}
objects非空判断(简化throw空指针异常)
public class Demo04Object {
public static void main(String[] args) {
method(null);
}
private static void method(Object obj) {
/*if(obj == null){
throw new NullPointerException("传递的对象的值是null");
}*/
//两个方法等同
Objects.requireNonNull(obj,"传递的对象的值是null");
}
}
声明异常throws关键字
throws关键字:异常处理的第一种方式,就是交给别人处理
作用:
会把异常对象的声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理-->中断处理
使用格式:在方法声明时使用
修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException{
throw new AAAException("产生原因");
throw new BBBException("产生原因");
}
注意:
1.throws关键字必须写在方法声明处
2.throws关键字后边声明的异常必须是Exception或者其子类
3.如果抛出多个异常,那么throws后面也必须声明多个异常
4.抛出的异常必须处理
要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM(在方法声明处在声明)
要么try...catch自己处理
public class Demo05Throws {
public static void main(String[] args) throws IOException {
//这里继续声明了异常,抛给JVM处理
readFile("c:\\a.txt");
}
注意:FileNotFoundException是编译异常,抛出了编译异常,
就必须处理这个异常,采用4中的两种方法
FileNotFoundException extends IOException 只需声明父类即可
public static void readFile(String fileName) throws IOException {
if(!fileName.equals("c:\\\\a.txt")){
throw new FileNotFoundException("传递的文件不是c:\\a.txt");
}
/*
如果传递的路径,不是.txt结尾
抛出IO异常对象
*/
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名异常");
}
}
}
try…catch处理异常
try..catch.异常处理的第二种方式,自己处理异常
格式:
try{
可能出现异常的代码
}catch(定义一个异常的变量,用来接受异常对象){
异常的处理逻辑
在工作中,一般会把异常的信息记录在一个日志中
}
【注意】1.try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
2.如果try中产生了异常,那么就会执行catch,然后执行之后的程序
3.如果没有产生异常,那么就不会执行catch,继续执行之后的代码
public class Demo02tryCatch {
public static void main(String[] args) {
try{
readFile("d:\\a.tx");
}
catch (IOException e){
System.out.println("文件的后缀不是.txt");
}
System.out.println("后续代码");
}
public static void readFile(String fileName) throws IOException {
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名异常");
}
System.out.println("路径没有问题");
}
}
Throwable类中定义了3中异常处理的方法(最好用)
- String getMassage() 返回此throwable的简短信息
- String toString()返回此throwable的详细消息字符串
- void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息时最全面的
public class Demo03Throwable {
public static void main(String[] args) {
try{
readFile("d:\\a.tx");
}
catch (IOException e){
e.printStackTrace();
//System.out.println(e.getMessage());
}
System.out.println("后续代码");
}
public static void readFile(String fileName) throws IOException {
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名异常");
}
System.out.println("路径没有问题");
}
}
finally代码块(当某个代码无论是否异常,都必须执行时)
格式:
try{
可能出现异常的代码
}catch(定义一个异常的变量,用来接受异常对象){
异常的处理逻辑
在工作中,一般会把异常的信息记录在一个日志中
}finally{
无论是否出现异常,该代码块都会执行
}
【注意事项】:1. 不能单独使用,必须和try…catch一起使用
2. 一般用于资源释放
public class Demo03Throwable {
public static void main(String[] args) {
try {
readFile("d:\\a.tx");
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("资源释放");
}
}
public static void readFile(String fileName) throws IOException {
if(!fileName.endsWith(".txt")){
throw new IOException("文件后缀名异常");
}
System.out.println("路径没有问题");
}
}
多个异常处理
- 分别处理(多try多catch)
public class Demo06Exception {
public static void main(String[] args) {
try {
int[] arr = {1,2,3};
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}
try{
List<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3);
System.out.println(list.get(4));
}catch (IndexOutOfBoundsException e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
- 多个异常一次捕获,多次处理(一try多catch)
【注意】:多catch若有子父类关系,则子类必须写在上面
public class Demo06Exception {
public static void main(String[] args) {
try {
int[] arr = {1,2,3};
System.out.println(arr[3]);
List<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3);
System.out.println(list.get(4));
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}catch (IndexOutOfBoundsException e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
- 一try一catch
多catch所对应的异常必须有父子关系,保留父类catch就ok
尽量不要在finally语句中写return语句
父类异常和子类异常的相统一
子父类的异常处理:
1.如果父类抛出多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常
2.父类方法没有抛出异常,子类重写父类方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,不能声明异常抛出
注意:父类异常是什么样,子类异常就怎么样
public class fu {
public void show01() throws NullPointerException,ClassCastException{}
public void show02() throws IndexOutOfBoundsException{}
public void show03() throws IndexOutOfBoundsException{}
//父类没有声明异常时,子类也不能声明异常,只能捕获处理
public void show04(){}
}
class zi extends fu{
//子类重写父类方法时,抛出和父类相同的异常
public void show01() throws NullPointerException,ClassCastException{}
//子类重写父类方法时,可以抛出父类异常的子类
public void show02() throws IndexOutOfBoundsException{}
//子类重写父类方法时,可以不抛出异常
public void show03(){}
//父类没有声明异常时,子类也不能声明异常,只能捕获处理
public void show04(){
try {
throw new Exception("编译异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义异常
自定义异常:
java提供的异常类,不够我们使用,需要自己定义一些异常类
格式:
public class XXXException extends Exception | RuntimeException{
添加一个带空参的构造方法
添加一个带异常信息的构造方法
}
注意:
1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2.自定义异常类必须继承Exception或者RuntimeException
继承Exception:说明是编译器异常,如果方法内部抛出了编译器异常,就必须处理这个异常,要么try...catch,要么throw
继承RuntimeException:运行期异常,无需处理,交给虚拟机
public class RegisterException extends Exception{
public RegisterException(){
super();
}
public RegisterException(String message)
{
super(message);
}
}
要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提醒:亲,该用户已被注册
public class Demo01RegisterException {
private static String[] usernames = new String[]{"张三","李四","王五"};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要注册的用户名:");
String next = sc.next();
checkUsername(next);
}
public static void checkUsername(String username) {
for (String s : usernames) {
if(s.equals(username)){
try {
throw new RegisterException("亲,该用户名已经被注册");
} catch (RegisterException e) {
e.printStackTrace();
return ;//结束方法,不运行下面的sout
}
}
}
System.out.println("恭喜您注册成功!");
}
}
并发和并行
并发:指两个或者多个事件在同一个时间段内发生(交替执行)
并行:指两个或多个事件在同一时间发生(同时发生)(同时执行)
线程与进程
进程:是指一个内存中运行的应用程序进入到内存的程序就是进程
线程:是进程中的一个执行单元,负责当前进程中程序的执行,例如:360管家内的清理垃圾
线程调度
- 分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用CPU的时间
- 抢占式调度:
优先让优先级高的线程使用CPU,若优先级相同,则随机选取一个执行,java是抢占式调度。 - 主线程:执行主方法的线程
执行从main方法开始,从上到下依次执行
JVM执行main方法,main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径叫做main(主)线程
创建多线程
创建多线程的第一种方式:创建Thread类的子类
java.lang.ThreadL类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Tread类中的run方法,设置线程任务(开启线程要做什么)
3.创建Thread类的子类对象
4.调用Thread类中的方法start方法,开启新的线程,执行run方法。
【注】:void start()使该线程开始执行,Java虚拟机调用该线程的run方法
结果是两个线程并发地运行;当前线程(main)和另一个线程(创建的新线程,执行其run方法).
多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。
main优先级更高先执行main线程,然后抢占式执行创建的线程
public class Demo01Thead extends Thread{
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("run1"+i);
}
}
}
public class MyThread {
public static void main(String[] args) {
Demo01Thead t = new Demo01Thead();
Demo02Thread s =new Demo02Thread();
s.start();
//t.run();
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:"+i);//main优先级更高先执行main线程,然后抢占式执行创建的线程
}
}
}
Thread类(线程类)
Thread中的常用方法
获取线程的名称:
1.使用Thread类中的方法getName()
String getName() 返回该线程的名字
2.可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
public class MyThread extends Thread{
//重写Thread方法中的run方法,设置线程任务
@Override
public void run(){
//获取线程名称,这里用的第一种方法
String name = getName();
System.out.println(name);
//第二种方法
Thread t = Thread.currentThread();
System.out.println(t);
//第三种方法
String name1 = t.getName();
System.out.println(name1);
}
}
public class GetThreadName {
public static void main(String[] args) {
MyThread mt = new MyThread();
new MyThread().start();
mt.start();
//获取主线程的名称
System.out.println(Thread.currentThread().getName());
}
}
main
Thread-0
Thread[Thread-0,5,main]
Thread-0
Thread-1
Thread[Thread-1,5,main]
Thread-1
设置线程名称
设置线程的名称:
1.使用Thread类中的方法setName(名字)
void setName(String name) 改变线程名称,使之与参数name相同
2.创建一个待参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,吧线程名称传递给父类
让父类(Thread)给子线程起一个名字Thread(String name)分配新的Thread对象
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){
super(name);//通过构造方法把线程名称传递给父类,让父类给子线程起一个名字
}
//重写run方法,写明要执行的任务
@Override
public void run(){
System.out.println(Thread.currentThread().getName());//输出线程名称
}
}
public class Demo01setThreadName {
public static void main(String[] args) {
MyThread t = new MyThread();
t.setName("x");//通过setName方法设置名称
t.start();
MyThread mt = new MyThread("小姐姐");//通过构造方法把线程名称传递给父类,让父类给子线程起一个名字
mt.start();
}
}
暂停线程
sleep(long millis, int nanos)
使正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行)
毫秒数结束之后,程序继续执行
public class Demo02Sleep {
public static void main(String[] args) {
for (int i = 0; i < 60; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
开启多线程的第二种方法:实现Runnable接口
创建多线程的第二种方法:实现Runnable接口
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写run方法来设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread(线程)类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
//2.在实现类中重写run方法来设置线程任务
@Override
public void run(){
//第二个线程
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
public class Demo01Runnable {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//4.创建Thread(线程)类对象,构造方法中传递Runnable接口的实现类对象
Thread t= new Thread(run);
//5.调用Thread类中的start方法,开启新的线程执行run方法
t.start();
//第一个线程
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
/*
main-->0
main-->1
main-->2
Thread-0-->0
Thread-0-->1
Thread-0-->2
*/
}
}
实现Runnable接口创建多线程程序的好处:
1. 避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),(但接口可以多个),
类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其他的类,
实现其他的接口
2. 增强了程序的扩展性,降低了程序的耦合性(解耦)
把设置线程任务和开启新线程进行了分离,可以写新的Runnable实现类来实现不同的任务
总之,第一种方法多个Thread子类,开启多线程,是用了继承的方式
第二种方法多个Runnable实现类,开启多线程,是用了接口的方式
线程安全问题
简述:三个窗口同时买1-100号票,可能出现买同一张票的情况
线程安全问题买票代码实现
public class RunnableImpl implements Runnable {
private int ticket = 20;
@Override
public void run(){
while(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
ticket--;
}
}
}
}
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
线程安全问题解决方法(同步代码块)
同步代码块解决线程安全问题:
格式:
synchronized(锁对象){
可能出现线程安全问题的代码块(访问共享数据的代码)
}
注意:
1.同步代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
==把同步代码块锁住,只让一个线程在同步代码块中执行==
把同步代码块锁住,只让一个线程在同步代码块中执行
public class RunnableImpl implements Runnable {
private int ticket = 20;
//创建一个锁对象
Object obj = new Object();
//设置线程任务卖票
@Override
public void run(){
while(ticket>0){
synchronized (obj){
//先判断票是否存在
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
ticket--;//票在卖,ticket--
}
}
}
}
}
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
同步技术的原理:使用一个锁对象,这个锁对象叫做同步锁,也叫对象锁,或者叫对象监视器
同步代码块中的线程,执行前会获取锁对象,没有执行完毕不会释放锁对象,同步代码块外的线程没有锁对象进入不了同步代码块,会处于阻塞状态,等待锁对象被释放
同步方法解决线程安全问题
使用同步方法解决线程安全问题:
使用步骤:
1.把访问了共享数据的代码抽取出来,放在一个方法中
2.在方法中添加synchronized修饰符
【注】:定义一个同步方法,同步方法会把方法内部的代码锁住,只让一个线程执行
同步对象的锁对象是实现类对象 new RunnableImpl,也就是this
public class RunnableImpl implements Runnable {
private int ticket = 20;
//创建一个锁对象
Object obj = new Object();
//设置线程任务卖票
@Override
public void run(){
//thisDemo05Threadsafe.RunnableImpl@1b6d3586与run对象的地址相同
System.out.println("this"+this);
while(true){
payTicket();
}
}
public synchronized void payTicket(){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
ticket--;//票在卖,ticket--
}
}
}
静态的同步方法
静态的同步方法锁对象不能是this,this是创建对象之后产生的,
静态方法优于对象(先进入内存中)
静态方法的锁对象是本类的class属性-->class文件(反射)
public class RunnableImpl implements Runnable {
private static int ticket = 20;
//创建一个锁对象
Object obj = new Object();
//设置线程任务卖票
@Override
public void run(){
while(true){
payTicket();
}
}
//静态方法要调用静态对象
public static synchronized void payTicket(){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
ticket--;//票在卖,ticket--
}
}
}
Lock锁解决线程安全问题
java.util.concurrent.l.Locks.Lock接口
使用Lock锁解决线程安全问题:
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
Locks接口常用方法:
void Lock(); 获取锁
void unLock(); 释放锁
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能出现安全问题的代码前调用Lock接口中的方法Lock获取锁
3.在可能出现安全问题的代码后调用Lock接口中的方法unLock释放锁
public class RunnableImpl implements Runnable {
private int ticket = 20;
Lock l =new ReentrantLock();
//设置线程任务卖票
@Override
public void run(){
System.out.println("this"+this);
while(true){
l.lock();
if(ticket>0){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"-->正在买第"+ticket+"张票");
ticket--;//票在卖,ticket--
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();//无论程序是否出现异常,都会把锁释放,会提高程序的效率
}
}
}
}
}
第三章 线程状态
3.1 等待唤醒案例
等待唤醒案例:线程之间的通信
1.创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃CPU的执行,
进入到waitting状态(无限等待状态)
2.创建一个老板线程(生产者): 花落5秒做包子,做好包子后,调用notify方法,唤醒顾客线程
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait()和notify方法
Object类中的方法
void wait()
导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notifyAll()
唤醒正在等待对象监视器的所有线程。
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//创建一个锁对象,并保证锁唯一
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("告诉老板我要多少个包子");
//调用wait方法,放弃cpu执行,进入到WAITTING状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子已经做好了,开冲");
}
}
}.start();
//创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//花费时间做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("包子做好,唤醒消费者线程");
obj.notify();//如果有多个线程,则会随机唤醒其中之一
}
}
}.start();
}
}
告诉老板我要多少个包子
包子做好,唤醒消费者线程
包子已经做好了,开吃
Object类中的wait(long m)方法
进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之间,没有被notify唤醒,则会自动醒来,线程进入到Runnable/Blocked状态
唤醒的方法:
1.notify() 唤醒正在等待对象监视器的单个线程。
2.notifyAll() 唤醒正在等待对象监视器的所有线程。
public class Demo02WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("顾客1告诉老板我要多少个包子");
//调用wait方法,放弃cpu执行,进入到WAITTING状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子已经做好了,顾客1开吃");
}
}
}.start();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("顾客2告诉老板我要多少个包子");
//调用wait方法,放弃cpu执行,进入到WAITTING状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子已经做好了,顾客2开吃");
}
}
}.start();
//创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//花费时间做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("包子做好,唤醒消费者线程");
obj.notifyAll();//可以把所有等待的线程唤醒
}
}
}.start();
}
}
等待与唤醒机制(案例)
包子类
资源类:包子
设置包子属性:皮,陷,
包子的状态:有true,无false
public class BaoZi {
//皮,陷
String pi;
String xian;
boolean flag = false;
}
包子铺类
生产者(包子铺)类:是一个线程类,可以继承Thread
设置线程任务(run):生产包子
对包子的状态进行判断
true :
包子铺调用wait方法进入到等待状态
false :
包子铺生产包子
增加一些趣味性:交替生产两种包子
有两种状态(i%2==0)
包子铺生产好包子
修改包子状态为true有
唤醒吃货线程,让吃货线程吃包子
注意:
包子铺线程和包子线程关系-->通信(互斥)
必须同时同步技术保证两个线程只能有一个在执行
锁对象必须保证唯一,可以使用包子对象作为锁对象
包子铺类和吃货类就需要把包子对象作为参数传递进来
1.需要在成员位置创建一个包子变量
2.使用带参数构造方法,为这个包子变量赋值
//生产者(包子铺)类:是一个线程类,可以继承Thread
public class BaoZiPu extends Thread{
//1.需要在成员位置创建一个包子变量
private BaoZi bz;
//2.使用带参数构造方法,为这个包子变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//设置线程任务(run):生产包子
public int count = 0;
@Override
public void run() {
while(true){
//以包子对象为锁对象
synchronized (bz){
if(bz.flag ==true){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子铺正在生产包子");
if(count%2==0){
bz.pi = "薄皮";
bz.xian = "三鲜馅";
}else{
bz.pi = "冰皮";
bz.xian = "牛肉大葱馅";
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//包子铺生产好包子
//修改包子状态为true有
count++;
bz.flag =true;
System.out.println("包子铺已经生产好了"+bz.pi+bz.xian+"包子");
//唤醒吃货线程,让吃货线程吃包子
bz.notify();
}
}
}
}
吃货类
消费者类(吃货类):是一个线程,可以继承Thread
设置线程任务run:吃包子
对包子的状态进行判断:
false:
吃货线程调用wait进入等待状态
true:
吃包子(counter--)
包子.flag=false;
唤醒包子铺线程
bz.notify();
public class ChiHuo extends Thread{
//1.需要在成员位置创建一个包子变量
private BaoZi bz;
//2.使用带参数构造方法,为这个包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag==false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("开始吃包子");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子已经吃完了");
bz.flag=false;
bz.notify();
System.out.println("---------------");
}
}
}
}
}
测试类
public class test {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
BaoZiPu bzp = new BaoZiPu(bz);
ChiHuo ch = new ChiHuo(bz);
bzp.start();
ch.start();
}
}
线程池
如果并发地线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,
这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
java线程池可以解决这种情况
Lambda表达式
函数式编程思想
面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
函数式编程思想:
只要能获取到结果,谁去做的不重要,怎么做的不重要,重要的是结果,不重视过程
Lambda表达式的格式:
(参数列表) -> {一段重写的代码}
说明:():接口中抽象方法的参数列表
{}:重写接口的抽象方法的方法体
public class Demo01Lambda {
public static void main(String[] args) {
//使用匿名内部类的方法实现多线程
new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"新的线程创建了");
}
}).start();
//使用Lambda表达式实现多线程
//使用()->代替过程,直接把所需要达到的目的传递给线程,
//()内是参数列表,->表示传递
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"新的线程创建了");
}).start();
}
}
Lambda表达式无参的案例
public class Demo01Cook {
public static void main(String[] args) {
invokeCook(()->{System.out.println("吃饭了");});
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
}
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
Lambda表达式有参的案例
Person类重写了toString方法
public class Demo01Array {
public static void main(String[] args) {
//使用数组存储多个Person对象
Person[] arr = {new Person("爱丽丝",18),
new Person("魔理沙",17),
new Person("四季映姬",16)
};
//根据年龄升序排序
/Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
//使用Lambda简化匿名内部类
//因为是有参数传递的,所以是有返回值的
Arrays.sort(arr,(Person o1, Person o2)->{return o1.getAge()-o2.getAge();});
//增强for循环遍历arr数组
for (Person p : arr) {
System.out.println(p);
}
}
}
【例2】
public interface Calculator {
public abstract int calc(int a,int b);
}
/*
给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值
*/
public class Demo01Lambda {
public static void main(String[] args) {
invokeCalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a+b;
}
});
//只要是匿名内部类,就可以使用Lambda方法简化
invokeCalc(20,30,(a,b)->{return a+b;});
}
public static void invokeCalc(int a,int b,Calculator c){
int sum = c.calc(a,b);
System.out.println(sum);
}
}
Lambda的省略格式
可以省略的内容:
1.(参数列表):括号中参数列表的参数类型可以省略
2.(参数列表):括号中如果只有一个参数,则可以省略括号
3.{一些代码}:如果代码只有一行,则无论是否有返回值都可以省略{},return,分号
且三个部分必须同时省略
invokeCalc(20,30,(a,b)->{return a+b;});
invokeCalc(20,30,(a,b)-> a+b);
【注】:1.使用Lambda必须具有接口(有些是内置的接口),且要求接口中有且仅有一个抽象方法
2.方法的参数或局部变量类型必须为Lambda对应的接口类型,
才能使用Lambda作为该接口的实例
File类(主要用于操作文件和文件夹)
文件和目录路径名的抽象表示
File类:
java把电脑中的文件和文件夹(目录)封装为一个File类,我们可以使用File类对文件和文件夹进行操作
可用于:创建一个文件/文件夹
删除文件/文件夹
获取文件/文件夹
判断文件/文件夹是否存在
对文件夹进行遍历
获取文件的大小
【注】:File类是与系统无关的类,任何的操作系统都可以使用这个类中的方法
重点:记住这三个单词
file:文件
directory:文件夹/目录
path:路径
File内的四个静态成员变量
File内的四个静态成员变量
static String pathSeparator 与系统相关的路径分隔符字符,为方便起见,表示为字符串
static char pathSeparatorChar 与系统相关的路径分隔符。
static String separator 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串
static char separatorChar 与系统相关的默认名称分隔符。
public class Demo01File {
public static void main(String[] args) {
String pathSeparator = File.pathSeparator;
System.out.println(pathSeparator);//路径分隔符 windows:分号; Linux:冒号:
String Separator = File.pathSeparator;
System.out.println(Separator);//文件名分隔符,windows:反斜杠\ linux:正斜杠/
}
}
绝对路径与相对路径
绝对路径:是一个完整的路径
以盘符(c:,D:)开始的路径
C:\\a.txt
D:\\IdeaProjects\\itcast\\123.txt
相对路径:是一个简化的路劲
相对于当前项目的根目录(D:\\IdeaProjects\\itcast\\123.txt),
对于使用当前项目根目录的文件,路劲可简写:(D:\\IdeaProjects\\itcast\\123.txt->123.txt)
【注意】:1.路劲不区分大小写
2.在windo中\表示转义字符,需要\\表示一个\
File类中的构造方法
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
.
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
参数:
pathname:字符串路劲名称
创建File类对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
public class Demo02File {
public static void main(String[] args) {
show1();
}
private static void show1() {
File f1 = new File("D:\\IdeaProjects\\itcast\\a.txt");//错的绝对路径
System.out.println(f1);
File f2 = new File("D:\\IdeaProjects\\itcast");//对的绝对路径
System.out.println(f2);
File f3 = new File("b.txt");//相对路径
System.out.println(f3);
/*
D:\IdeaProjects\itcast\a.txt
D:\IdeaProjects\itcast
b.txt
*/
}
}
public static void main(String[] args) {
show1();
show2("c:\\","a.txt");
}
private static void show2(String parent ,String child) {
/*
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
parent:父路径
child:子路径
*/
File f2 = new File(parent,child);
System.out.println(f2);
}
public static void main(String[] args) {
show1();
show2("c:\\","a.txt");
File f4 = new File("c://");
show3(f4,"d.txt");
}
private static void show3(File parent, String child) {
//File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
File f3 = new File(parent,child);
System.out.println(f3);
}
File中的常用方法
常用方法:
1.long length() 返回由此抽象路径名表示的文件的大小。返回文件
2.String getPath() 将此抽象路径名转换为路径名字符串。路径是什么样就返回什么样
3.String getName() 返回由此抽象路径名表示的文件或目录的名称。获取路径结尾部分
4.File getAbsoluteFile() 返回此抽象路径名的绝对形式。必定返回绝对路径
private static void show01() {
//File getAbsoluteFile() 返回此抽象路径名的绝对形式。
File f1 = new File("D:\\IdeaProjects\\itcast\\a.txt");
File absoluteFile = f1.getAbsoluteFile();
System.out.println(absoluteFile);
File f2 = new File("a.txt");
File absoluteFile1 = f2.getAbsoluteFile();
System.out.println(absoluteFile1);//重写了toString方法
/*
D:\IdeaProjects\itcast\a.txt
D:\IdeaProjects\itcast\a.txt
*/
}
File中判断功能的方法
File判断功能的方法:
-boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
-boolean isDirectory() 测试此抽象路径名表示的文件是否为目录(文件夹),是否以文件夹结尾。
-boolean isFile() 测试此抽象路径名表示的文件是否为普通文件,是否是以文件结尾。
public class Demo04File{
public static void main(String[] args) {
show1();
show2();
}
private static void show2() {
File f3 =new File("D:\\IdeaProjects\\itcast");
//判断文件是否存在
if(f3.exists()){
System.out.println(f3.isDirectory());//true
System.out.println(f3.isFile());//false
}
}
private static void show1() {
File f1 = new File("D:\\IdeaProjects\\itcast");//true
System.out.println(f1.exists());
File f2 = new File("D:\\IdeaProjects\\itcast\\shuan");//false
System.out.println(f2.exists());
}
}
File类中创建与删除的方法
1.boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,原地创建一个由该抽象路径名命名的新的空文件。(只能用于创建文件)
2.boolean delete() 删除由此抽象路径名表示的文件或目录。
3.boolean mkdir() 创建由此抽象路径名命名的目录。
4.boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
public class Demo05File {
public static void main(String[] args) throws IOException {
show01();
show02();
}
private static void show02() {
File f2 =new File("itcast\\src\\Demo13File\\aaa");
System.out.println(f2.mkdir());
File f3 = new File("itcast\\src\\Demo13File\\111\\222\\333");
System.out.println(f3.mkdirs());
System.out.println(f3.delete());
}
private static void show01() throws IOException {
//createNewFile(),只用于创建文件
File f1 =new File("D:\\IdeaProjects\\itcast\\src\\Demo13File\\1.txt");
boolean f = f1.createNewFile();
System.out.println(f);
System.out.println(f1.delete());//直接在硬盘上删除,不走回收站;
File f2 =new File("itcast\\src\\Demo13File\\3.txt");
boolean fz = f2.createNewFile();
System.out.println(fz);
System.out.println(f2.delete());
}
}
File的遍历操作
1.String[] list() 返回一个字符串数组,表示的目录中的所有子文件和目录。
2.File[] listFiles() 返回一个抽象路径名数组,表示该路径名表示的目录中的所有子文件和目录
若给出的目录的路径不存在或者不是目录,则抛出空指针异常
public class Demo06File {
public static void main(String[] args) {
//show01();
show02();
}
private static void show02() {
File f1 = new File("D:\\IdeaProjects\\itcast\\src\\Demo13File");
File[] files = f1.listFiles();
for (File file : files) {
System.out.println(file);
}
}
private static void show01() {
File f1 = new File("D:\\IdeaProjects\\itcast\\src\\Demo13File");
String[] list = f1.list();
for (String s : list) {
System.out.println(s);
}
}
递归
概念,分类
直接递归:
main(){
a();
}
a(){
a();
}
间接递归:
main(){
b();
}
b(){
c();
}
c(){
b();
}
注意事项:
1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出
2.在递归中虽然有限定条件,但是递归次数不能太多次,否则也会发生栈内存溢出
3.构造方法,禁止递归。
递归的使用前提:
当调用方法的时候,方法的主体不变,每次调用方法的参数不同,就可以使用递归
public class Demo14DiGui {
public static void main(String[] args) {
a(1);
}
/*
构造方法,禁止递归
编译报错:构造方法是创建对象使用的,一直递归,会产生无数个对象,直接编译报错
*/
public Demo14DiGui(){
Demo14DiGui();
}
private static void a(int i) {
System.out.println(i);
//限定条件
if(i==10000){
return;
}
a(++i);
}
}
递归解决1到n之和
递归常用思想:
1+2+3+…+n等同于
n+(n-1)+(n-2)+…+1;
public class Demo01DiGui {
public static void main(String[] args) {
System.out.println("请输入n的值");
Scanner sc =new Scanner(System.in);
int n = sc.nextInt();
int sum = sum(n);
System.out.println(sum);
}
/*
递归常用思想:
1+2+3+...+n等同于
n+(n-1)+(n-2)+...+1;
*/
private static int sum(int n) {
if(n==1) {
return 1;
}
return n+sum(n-1);
}
}
递归求阶乘
递归常用思想:
n! = n*(n-1)!
public class Demo02DiGui {
public static void main(String[] args) {
System.out.println("请输入n的值");
Scanner sc =new Scanner(System.in);
int n = sc.nextInt();
int sum = sum(n);
System.out.println(sum);
}
private static int sum(int n) {
if(n==1){
return 1;
}
return n*sum(n-1);
}
}
递归打印多级目录
public class Demo03DiGui {
public static void main(String[] args) {
File file = new File("D:\\IdeaProjects\\itcast\\src");
getAllFile(file);
}
private static void getAllFile(File file) {
File[] files = file.listFiles();//获取该文件夹内的所有文件
for (File file1 : files) {
if(file1.isDirectory()){//判断是否是文件夹
getAllFile(file1);
}else{
System.out.println(file1);
}
}
}
}
递归打印以.java结尾的文件名
练习:
递归打印多级目录
需求:
遍历文件夹,及其文件夹的子文件夹
只输出.java文件
public class Demo15FileSearch {
public static void main(String[] args) {
File file = new File("D:\\IdeaProjects\\itcast\\src");
getAllFile(file);
}
private static void getAllFile(File dir) {
File[] file = dir.listFiles();
for (File f : file) {
if(f.isDirectory()){
getAllFile(f);
}else {
//1.把文件转换为字符串
//String path = f.getPath();//默认调用的是toString方法
//String s = f.toString();//会输出完整路径
String name = f.getName();//abc.java只返回文件名
//把字符串转换为小写
name = name.toLowerCase();
//2.调用String类中的方法endsWith()方法判断字符串是否是以.java结尾
boolean b = name.endsWith(".java");
if (b){
System.out.println(name);
}
/*
if(f.getName().toLowerCase().endsWith(".java")){
System.out.println(name);
}
*/
}
}
}
}
文件过滤器
使用过滤器来对文件过滤
File[] listFiles(FileFilter filter)
java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器。
作用:用来过滤文件(File文件)
抽象方法:
boolean accept(File pathname) 测试指定抽象路径名是否应该包含在某个路径名列表中。
参数:
File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象
File[] listFiles(FilenameFilter filter)
java.io.FilenameFilter接口
作用:用于过滤文件的名称
抽象方法:用来过滤文件的方法
boolean accept(File dir, String name)
测试指定文件是否应包含在文件列表中。
参数
dir - 找到该文件的目录。
name - 文件的名称。
结果
true当且仅当该名称应包含在文件列表中时; false否则。
过滤器原理:
listFiles方法自动会调用FileFilterImpl过滤器中的accept方法
把dir中的每一个文件或者文件夹传递给accept中的pathname
如果accept方法返回的是true,则会将该pathname传递给f【】数组中保存下来
所以可以在accept方法中书写过滤原则
过滤器
public class FileFilterImpl implements FileFilter {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()){
return true;
}else{
return pathname.getName().toLowerCase().endsWith(".java");
}
}
}
主程序
public class Demo04Filter {
public static void main(String[] args) {
File file = new File("D:\\IdeaProjects\\itcast\\src");
getAllFile(file);
}
/*
过滤器原理:
listFiles方法自动会调用FileFilterImpl过滤器中的accept方法
把dir中的每一个文件或者文件夹传递给accept中的pathname
如果accept方法返回的是true,则会将该pathname传递给f【】数组中保存下来
所以可以在accept方法中书写过滤原则
*/
private static void getAllFile(File dir) {
File[] f = dir.listFiles(new FileFilterImpl());
for (File file : f) {
if (file.isDirectory()){
getAllFile(file);
}else{
String name = file.getName();
System.out.println(name);
}
}
}
}
【例二】
public class Demo04Filter {
public static void main(String[] args) {
File file = new File("D:\\IdeaProjects\\itcast\\src");
getAllFile(file);
}
/*
过滤器原理:
listFiles方法自动会调用FileFilterImpl过滤器中的accept方法
把dir中的每一个文件或者文件夹传递给accept中的pathname
如果accept方法返回的是true,则会将该pathname传递给f【】数组中保存下来
所以可以在accept方法中书写过滤原则
*/
private static void getAllFile(File dir) {
//采用FileFilter过滤器
File[] f = dir.listFiles(new FileFilterImpl(){
@Override
public boolean accept(File pathname) {
return pathname.isDirectory()||pathname.getName().toLowerCase().endsWith(".java");
}
});
//采用FileFilter过滤器并用Lambda表达式简写
File[] f = dir.listFiles((File pathname)->{
return pathname.isDirectory()||pathname.getName().toLowerCase().endsWith(".java");
}
);
//采用FilenameFilter过滤器
File[] f2 = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//File(dir,name)其中dir是头文件名,name是尾文件名,合在一起组成文件名
return new File(dir,name).isDirectory()||name.toLowerCase().endsWith(".java");
}
});
for (File file : f2) {
if (file.isDirectory()){
getAllFile(file);
}else{
String name = file.getName();
System.out.println(name);
}
}
}
}
IO流
i:input 输入(读取)
o:output 输出(写入)
流:数据流
字节流
字节输出流【OutputStream】(给文件写内容)
java.io.OutputStream:此抽象类是表示输出字节流的所有类的超类
以下是其子类都可用的方法:
void close() 关闭此输出流并释放与此流相关联的任何系统资源。
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。
void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。
void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
abstract void write(int b) 将指定的字节写入此输出流。
public class FileOutputStream extends OutputStream
构造方法:
FileOutputStream(File file) 创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(String name) 创建文件输出流以指定的名称写入文件。
参数:写入文件的目的地(输出流,即把数据输出到目的地)
String name:目的地是一个文件的路径
File file:目的地是一个文件
构造方法的作用:
1.创建一个FileOutputStream对象
2.会根据构造方法中传递的文件/文件路径,创建一个空的文件
3.会把FileOutputStream对象指向创建好的文件
写入数据的原理(内存-->硬盘)
java程序-->JVM虚拟机-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中
字节输出流的使用步骤:
1.创建一个FileOutputStream对象,构造方法中传递写入数据的路径
2.调用FileOutputStream对象中的方法write,把数据写入到文件中
3.释放资源(流使用会占用一定资源,使用后要清空内存,提高程序效率)
public class Demo01OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\itcast\\src\\Demo15IOStream\\a.txt");
fos.write(105);//在此写入会重新覆盖
fos.close();
}
}
给文本写入多个字节
void write(byte[] b) 将 b.length个字节从指定的字节数组写入此文件输出流。
一次写入多个字节:
如果写的第一个字节是正数(0-127),那么显示的时候回查询ASCII表
如果写的第一个字节是负数,那第一个字节会和第二个字节,两个字节组成一个中文显示。查询系统默认码表(GBK)
void write(byte[] b, int off, int len) 把字节数组的一部分写入到文件中。
off:开始索引
len:写几个
/*
一次写多个字节
*/
public class Demo02OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\itcast\\src\\Demo15IOStream\\a.txt");
byte[] bytes = {-65,-66,-67,-68,-69};//烤郊
fos.write(bytes);
fos.close();
byte[] bytes1 ="你好".getBytes();
fos.write(bytes1);//你好
fos.close();
}
}
字节输出流的续写和换行
构造方法:
1.FileOutputStream(File file, boolean append)
创建一个向指定name文件中写入数据的文件输出流
2.FileOutputStream(String name, boolean append)
创建一个向指定File对象表示的文件中写入数据的文件输出流
参数:
file:目标文件
name:目标文件的字符串名
append:是否续写开关
true:在源文件末尾续写文件
false:创建一个新文件,覆盖源文件
写换行:写换行符号
windows:\r\n
linux:/n
mac:/r
public class Demo03OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\itcast\\src\\Demo15IOStream\\a.txt",true);
byte[] bytes ="加油".getBytes();
for (int i = 0; i < 10; i++) {
fos.write(bytes);
fos.write("/r/n".getBytes());
}
fos.close();
}
}
字节输入流
java.io.InputStream :字节输入流
子类共性方法:
abstract int read() 从输入流读取数据的下一个字节。
int read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区b。
void close() 关闭此输入流并释放与流相关联的任何系统资源。
常用子类:
public class FileInputStream extends InputStream
作用:吧硬盘中的文件中的数据,读取到内存中使用
构造方法:
FileInputStream(File file)通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的 File对象file命名。
FileInputStream(String name)通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。
构造方法作用:
读取文件的数据流
1.会创建一个FileInputStream的对象
2.会把FileInputStream对象指向构造方法中要读取的文件
读取数据的原理(硬盘-->内存):
java程序-->JVM虚拟机-->OS(操作系统)-->OS调用读取数据的方法-->读取文件
读取文件步骤:
1.
调用构造方法会自动把指针指向文件第一位
每使用read()一次指针会往后移一位
public class Demo04InputStream {
public static void main(String[] args) throws IOException {
FileInputStream fis =new FileInputStream("itcast\\src\\Demo15IOStream\\a.txt");
//int read()读取文件的一个字节并返回,若读取到文件末尾则返回-1
int len = 0;
while((len = fis.read())!= -1){
System.out.println((char)len);//abc
}
fis.close();
}
}
字节输入流读取多个字节
字节输入流一次读取多个字节的方法:
int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
String(byte[] bytes):把字节数组转换为字符串
String(byte[] bytes, int offset, int length):把字节数组的一部分转换为字符串
public class Demo05InputStream {
public static void main(String[] args) throws IOException {
//创建FillInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("itcast\\src\\Demo15IOStream\\a.txt");
//使用FileInputStream对象中的方法read读取文件
//int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
byte[] bytes = new byte[2];
int len = fis.read(bytes);//把fis指向的文件读取到bytes数组中
System.out.println(len);//2
System.out.println(Arrays.toString(bytes));//[65, 66]
System.out.println(new String(bytes));//AB
/*
String(byte[] bytes):把字节数组转换为字符串
String(byte[] bytes, int offset, int length):把字节数组的一部分转换为字符串
*/
//释放资源
fis.close();
}
}
文件复制
文件复制练习:一读一写
只要是文件,就可以用字节流读写文件
public class Demo06CopyFile {
public static void main(String[] args) throws IOException {
long s =System.currentTimeMillis();
FileOutputStream fos = new FileOutputStream("D:\\1.jpg");
FileInputStream fis = new FileInputStream("C:\\1.jpg");
int len =0;
byte[] bytes = new byte[65536];
//利用一个byte型数组,使得一次读写可以读取多个bit,不用大量调用while循环
//len = -1时文件已经读完
while((len = fis.read(bytes))!=-1){
fos.write(bytes);
}
//释放资源(先关闭写的,在关闭读的,因为写完了,肯定读完了)
fos.close();
fis.close();
long e = System.currentTimeMillis();
System.out.println("复制文件共耗时:"+(e-s)+"ms");
}
}
字符流
字符输入了以字符为单位读取文件,便于读取中文文本,因为,一个中文字符占多个字节
字符输入流【Reader】
java.io.Reader:字符输入流
共性的成员方法:
1.int read() 读一个字符
2.int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
3.abstract void close() 关闭流并释放与之相关联的任何系统资源。
java.io.FileReader extends InputStreamReader extends Reader
FileReader:文件字符输入流
作用:把硬盘文件中的数据以字符的方式读取到内存中
构造方法:
FileReader(File file)创建一个新的FileReader,给出 File读取。
FileReader(String fileName) 创建一个新的FileReader,读取fileName同名文件。
FileReader构造方法的作用:
1.创建一个FileReader对象
2.会把FileReader对象指向要读取的文件
public class Demo07Reader {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("itcast\\src\\Demo15IOStream\\a.txt");
int len = 0;
//调用char类型数组装数据避免多次调用循环
char[] c =new char[1024];
while((len = fr.read(c)) != -1){
//String类中有一个构造方法可以把字符型数组转化为字符串
//String(char[] value, int offset, int count)
//offset开始位置,count有效个数
System.out.println(new String(c,0,len));
}
fr.close();
}
}
字符输出流
java.io.writer:字符输出流
共性的成员方法:
abstract void close() 关闭流,先刷新。
abstract void flush()刷新流。
void write(char[] cbuf)写入一个字符数组。
abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分。
void write(int c)写一个字符
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分。
Writer append(char c) 将指定的字符附加到此文件。
public class FileWriter extends OutputStreamWriter:
FileWriter:文件字符输入流
作用:把内存中字符数据写入到文件中
构造方法:
1.FileWriter(File file) 给一个File对象构造一个FileWriter对象。
2.FileWriter(String fileName) 构造一个给定文件名的FileWriter对象。
构造方法:
1.会创建一个FileWriter对象
2.会根据构造方法中传递的文件/文件路径,创建文件
3.会把FileWriter对象指向创建好的文件
使用步骤:
1.创建FileWriter对象,构造方法中绑定要写入的数据的目的地
2.使用FileWriter中的方法write,把数据写入到内存缓冲区(字符)
3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中(close方法自带flush)
4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
public class Demo08Writer {
public static void main(String[] args) throws IOException {
FileWriter fw =new FileWriter("itcast\\src\\Demo15IOStream\\a.txt");
fw.write("你好世界");
fw.flush();//刷新内存缓冲区
fw.write(97);
fw.write(new char[]{'1','2','3'});
fw.close();//close方法之后流已经关闭,不能再写入数据
}
}
input/output 输入/输出 -->读/写–>read/write
字符输出流的续写和换行
字符输出流续写:
1.FileWriter(File file, boolean append) boolean为true时续写,false时非续写
2.FileWriter(String fileName, boolean append)
换行:
windows:\r\n
linux:/n
mac:/r
public class Demo09Writer {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt",true);
fw.write("\r\n"+"你好");
fw.write("\r\n"+"我吐了");
fw.close();
}
}
使用try…catch…finally处理文件异常
public class IOtryCatch {
public static void main(String[] args) {
//提高变量fw的作用域,让finally可以使用
//fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt",true);
//若执行失败,则fw没有值,fw.close会报错
FileWriter fw=null;
try {
fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt",true);
for (int i = 0; i < 5; i++) {
fw.write("\r\n"+"Hello World");
}
}catch (IOException e) {
System.out.println(e);
}finally {
if(fw!=null){
//如果创建对象失败,fw的默认的值就是null,null是不能调用方法的,会抛出空指针异常,所以需要增加一个判断,不是null在把资源释放
try {
//fw.close()本身可能出现异常
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用try…catch…finally处理文件异常的改进方式
public class Demo10 {
public static void main(String[] args) {
//这个改进的异常处理方式,不用fis.close(),fos.close(),自带清除缓存
try(FileInputStream fis = new FileInputStream("C:\\1.jpg");
FileOutputStream fos = new FileOutputStream("D:\\1.jpg");)
{
int len = 0;
byte[] bytes = new byte[1024];
while((fis.read(bytes))!=-1){
fos.write(bytes);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
属性集
使用Propertes集合存储数据集(把数组内容存储进文件中store,把文件内容读取到数组中load)
java.util.Properties集合extends Hashtable<k,v>
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
Properties集合中一些操作字符串的特有的方法:
1.String setProperty(String key) 调用Hashtable的方法put
2.Object getProperty(String key, String value)通过key找到value的值,相当于Map集合中的get(key)方法
3.Set<String> stringPropertyNames()返回属性列表中的键集,其中该键机器对应值是字符串,此方法相当于Map集合的keySet方法
public class Demo01Properties {
public static void main(String[] args) {
Properties prop = new Properties();
prop.setProperty("miku","168");
prop.setProperty("luka","165");
prop.setProperty("aki","170");
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
String value = prop.getProperty(key);
System.out.println(key+":"+value);
}
}
}
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
1.void store(Writer writer, String comments)
2.void store(OutputStream out, String comments)
参数:
OutputStream out 字节输出流,不能有中文
Writer writer 字符输出流,可以有中文
String comments 注释,不能使用中文,默认为Unicode编码
一般使用""空字符串
使用步骤:
1.创建Properties集合对象,添加数据
2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
3.释放Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
4.释放资源
private static void show02() throws IOException {
Properties prop = new Properties();
prop.setProperty("miku","168");
prop.setProperty("luka","165");
prop.setProperty("aki","170");
FileWriter fw = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt");
prop.store(fw,"sava data");
fw.close();
}
读取文件数据到集合中
private static void show03() throws IOException {
/*
load方法与store方法类似:
1.void load(InputStream inStream)
2.void load(Reader reader)
步骤:
1.创建Properties集合
2.使用Properties集合对象的方法load读取保存键值的文件
3.遍历Properties
*/
Properties prop = new Properties();
FileReader fr =new FileReader("itcast\\src\\Demo15IOStream\\a.txt");
prop.load(fr);
Set<String> s = prop.stringPropertyNames();
for (String key : s) {
String value = prop.getProperty(key);
System.out.println(key+":"+value);
}
fr.close();
}
缓冲流
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率
java.io.BufferedOutputStream extends OutputStream
BufferedOutputStream:字节缓冲输出流
继承自父类的共性成员方法:
void close() 关闭此输出流并释放与此流相关联的任何系统资源。
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。
void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。
void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
abstract void write(int b) 将指定的字节写入此输出流。
构造方法:
1.BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
2.BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。
参数:
OutputStream out:字节输出流,可以传递FileOutputStream等字节输出流
int size:指定缓冲区内部缓冲区的大小,
使用步骤:
1.创建一个字节输出流
2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutStream对象效率
3.使用BufferedOutputStream对象中的write方法,把数据写入到内部缓冲区中
4.释放资源(会先调用flush方法刷新数据,把数据刷新到文件中)
public class Demo01BufferedOutputStream {
public static void main(String[] args) throws IOException {
long s1 = System.currentTimeMillis();
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
BufferedOutputStream nos = new BufferedOutputStream(fos);
nos.write("我把数据写入到内部缓冲区中".getBytes());
nos.flush();
nos.close();
long s2 = System.currentTimeMillis();
System.out.println((s2-s1)+"ms");
}
}
缓冲流复制文件提高效率
public class Demo01BufferedOutputStream {
public static void main(String[] args) throws IOException {
long s1 = System.currentTimeMillis();
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
BufferedOutputStream nos = new BufferedOutputStream(fos);
nos.write("我把数据写入到内部缓冲区中".getBytes());
nos.flush();
nos.close();
long s2 = System.currentTimeMillis();
System.out.println((s2-s1)+"ms");
}
}
BufferedWriter字符缓冲输出流
继承自父类的共性成员方法:
abstract void close() 关闭流,先刷新。
abstract void close() 关闭流,先刷新。
abstract void flush()刷新流。
void write(char[] cbuf)写入一个字符数组。
abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分。
void write(int c)写一个字符
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分。
Writer append(char c) 将指定的字符附加到此文件。
构造方法:
1.BufferedWriter(Writer out)
2.BufferedWriter(Writer out, int sz)
参数:
Writer out:字符输出流Writer
int sz:缓冲区大小
特有的成员方法:
void newLine() 写一个行分隔符。会根据不同的操作系统,获得不同的行分隔符
public class Demo03BufferedWriter {
public static void main(String[] args) throws IOException {
FileWriter fos = new FileWriter("itcast\\src\\Demo15IOStream\\a.txt");
BufferedWriter bw = new BufferedWriter(fos);
bw.write("你吃饱了吗?");//writer系方法可以直接用字符串,outputStream系方法必须先getBytes()
bw.close();
}
}
BufferedReader字符缓冲输入流
java.io.BufferedReader extends Reader
继承自父类的方法:
1.int read() 读一个字符
2.int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
3.abstract void close() 关闭流并释放与之相关联的任何系统资源。
构造方法:
BufferedReader(Reader in)
BufferedReader(Reader in, int sz)
参数:
Reader in 字符缓冲输入流Reader系
int sz:字符缓冲区大小
特有的成员方法:
String readLine() 读一行文本。
返回值:若读到最后一行,这返回null
public class Demo04BufferedReader {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("itcast\\src\\Demo15IOStream\\a.txt");
BufferedReader br = new BufferedReader(fr);
//第一种方式
char[] c =new char[1024];
int len=0;
while((len = br.read(c))!= -1){
System.out.println(new String(c));
}
//第二种方式
String line ;
while(( line = br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
文本内容排序(以.为分隔符)
练习:
对文本进行排序
分析:
1.创建一个HashMap集合,key存储序号,value存储内容
2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
4.使用字符缓冲输入流中的方法readLine,逐行读取文本(以\n换行符为分隔)
5.对读取好的文本进行切割,获取行中的序号和文本内容
6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序)
7.遍历HashMap集合,获取每一个键值对
8.把每一个键值对拼接成文本行
9.通过字符输出流中的方法write,把拼接好的文本写入到文件中
10.释放资源
public class Demo05Test {
public static void main(String[] args) throws IOException {
//准备好数组,字符输入/输出缓冲流
HashMap<String,String> map = new HashMap<>();
BufferedWriter bw = new BufferedWriter(new FileWriter("itcast\\src\\demo17Buffered\\a.txt"));
BufferedReader br = new BufferedReader(new FileReader("itcast\\src\\Demo15IOStream\\a.txt"));
String line ;
//分隔行内容,并分别作为键和值添加到map集合中
while((line = br.readLine())!=null){
String[] arr = line.split("\\.");
map.put(arr[0],arr[1]);
}
//待map集合将键排序好后(map集合会自动排序),将排序好后的内容写入目标文本中
for (String key : map.keySet()) {
String value = map.get(key);
line = key+"."+value;
bw.write(line);
bw.newLine();
}
br.close();
bw.close();
}
}
转换流
ASCII:基本编码表
GBK:最常用的中文编码表
unicode:万国码,有UTF-8,UTF-16,UTF-32三种编码方案,
IDE使用的是UTF-8编码格式
windows系统中文版使用的是GBK,
转换流的原理
字节输入流FileInputStream只能读取UTF-8格式的文本,并且是两个字节两个字节读取
字符输入流FileReader也只能读取UTF-8格式的文本,并且是三个三个字节一读
InputStreamReader是字节流通向字符流的桥梁,可以把字节输入流FileInputStream转换为查询指定码表的输入流(GBK,UTF-8…)
OutputStreamWriter与InputStreamReader类似
OutputStreamWriter指定编码格式的字符输出流
java.io.OutputStreamWriter extends Writer
OutputStreamWriter:是字符流通向字节流的桥梁,可以使用指定的编码方式(GBK,UTF-8)
继承自父类的成员方法:
abstract void close() 关闭流,先刷新。
abstract void flush()刷新流。
abstract void write(char[] char, int off, int len)写入字符数组的一部分。
void write(String str, int off, int len)写一个字符串的一部分。
Writer append(char c) 将指定的字符附加到此文件。
构造方法:
OutputStreamWriter(OutputStream out, String charsetName)
创建一个使用命名字符集的OutputStreamWriter。
参数:
OutputStream out:字节输出流
String charsetName:自定的编码表名称
使用步骤:
1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码,转换为指定编码格式)
3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中
4.释放资源
public class demo06OutputStreamWrite {
public static void main(String[] args) throws IOException {
//writeUTF8();
writeGBK();
}
private static void writeGBK() throws IOException {
//OutputStreamWriter相当于一个转换器,把2字节UFT-8的读取文本方式转换为其他读取方式
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\GBK.txt"),"gbk");
osw.write("坎公骑冠剑");
osw.flush();
osw.close();
}
private static void writeUTF8() throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\utf-8.txt"),"utf-8");
osw.write("你好");
osw.flush();
osw.close();
}
}
InputStreamReader指定编码格式的字符输入流
java.io.InputStreamReader extends Reader
InputStreamReader:是字节流通向字符流的桥梁,可以使用指定的编码方式(GBK,UTF-8)
继承自父类的成员方法:
1.int read() 读一个字符
2.int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
3.abstract void close() 关闭流并释放与之相关联的任何系统资源。
构造方法:
InputStreamReader(InputStream in, String charsetName)
创建一个使用命名字符集的InputStreamReader。。
参数:
InputStream in:字节输入流
String charsetName:指定的编码表名称,不区分大小写
使用步骤:
1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
2.使用InputStreamReader对象中的方法read读取文件
3.释放资源
public class Demo07InputStreamReader {
public static void main(String[] args) throws IOException {
read();
}
private static void read() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("itcast\\src\\Demo15IOStream\\a.txt"),"utf-8");
int len = 0;
char[] c = new char[1024];
//读取的内容会放置在c数组中
while((len=isr.read(c))!=-1){
System.out.println(c);
}
isr.close();
}
}
转换文件编码
练习:转换文件编码
将GBK编码的文本文件,转换为UFT-8编码的文本文件
public class Demo08test {
public static void main(String[] args) throws IOException {
//1.创建InputStreamReader对象,构造方法中传递字节输入流和指定编码表GBK
InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\我是GBK文本.txt"),"GBK");
//2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表UTF-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\我是UTF-8文本.txt"),"UTF-8");
//isr对象的方法read读取文件,数据存储打c数组,然后把c数组保存的内容写入目标文本
int len = 0;
char[] c =new char[1024];
while((len = isr.read(c))!=-1){
osw.write(c);
}
isr.close();
osw.close();
}
}
序列化流
把对象以流的方式,写入到文本中保存,叫做序列化流(ObjectOutputStream)
把保存在文本中的对象,以流的形式读取出来,叫做反序列化流(ObjecInputStream)
序列化ObjectOutputStream(把实例对象写入文本中)
java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream:对象的序列化
作用:把对象以流的方式写入到文本中保存
构造方法:
ObjectOutputStream(OutputStream out)
创建一个写入指定的OutputStream的ObjectOutputStream。
特有的成员方法:
void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。
使用步骤:
1.创建ObjectOutputStream对象,构造方法中传递字节输入流
2.使用ObjectOutputStream对象中的方法writeObject,把对象写入文件中
3.释放资源
NotSerializableException没有序列化异常
Serializable接口也叫做标记型接口
要进行序列化和反序列化的类必须实现Serializable接口,该接口会给类添加一个标记
有:就可以序列化和反序列化
无:就会抛出NotSerializableException异常
相对于一种权限
public class demo01ObjectOutputStream {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("itcast\\src\\demo17Buffered\\a.txt"));
Person p =new Person("博丽灵梦",16);
oos.writeObject(p);
oos.close();
}
}
对象的反序列化ObjectInputStream
public class Demo02ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("itcast\\src\\demo17Buffered\\a.txt"));
Object person = ois.readObject();
ois.close();
System.out.println(person);
}
}
transient关键字(被修饰的成员变量不能被序列化和反序列化)
static关键字:静态关键字
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
被static修饰的成员变量不能被序列化,序列化对的都是对象
静态不属于对象,他被所有对象所共享
transient关键字:瞬态关键字
被transient关键字修饰的成员变量,不能被序列化
例如:private transient int age;
InvalidClassException
问题:每次修改类的定义,都回给class文件生成一个新的序列号
解决方案:无论是否对类的定义进行修改,都不重新生成新的序列号,可以手动给类添加一个序列号
例:static final long seriaVersionUID = 42L
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
序列化集合(保存多个对象)
练习:序列化集合
当我们想保存多个对象的时候
可以把多个对象存储到一个集合中
对集合进行序列化和反序列化
分析:
1.定义一个存储Person对象的ArrayList集合
2.在ArrayList集合中添加Person类
3.创建并使用序列化流ObjectOutputStream对象中的方法writeObject,对集合进行序列化
4.创建并使用反序列化流ObjectInputStream对象中的方法readObject读取文件中保存的集合
5.把Object类型的集合转换为ArrayList类型
6.遍历集合,释放资源
练习:序列化集合
当我们想保存多个对象的时候
可以把多个对象存储到一个集合中
对集合进行序列化和反序列化
分析:
1.定义一个存储Person对象的ArrayList集合
2.在ArrayList集合中添加Person类
3.创建并使用序列化流ObjectOutputStream对象中的方法writeObject,对集合进行序列化
4.创建并使用反序列化流ObjectInputStream对象中的方法readObject读取文件中保存的集合
5.把Object类型的集合转换为ArrayList类型
6.遍历集合,释放资源
PrintStream 打印流
java.io.PrintStream:打印流
PrintStream:为其他输出流添加了功能,使他们能方便的打印各种数值表现形式
PrintStream特点:
1.只负责数据的输出,不负责数据的读取
2.PrintStream从不抛出IOException
3.其特有的方法:
print(任意类型的值)
println(任意类型的值并换行)
构造方法:
1.PrintStream(File file)
2.PrintStream(OutputStream out)
3.PrintStream(String fileName)
注意:
如果使用的是继承自父类的write方法写数据,那么查看数据的时候回查询编码表,例如:97->a
如果使用的是自带的方法print写数据,则数据原样输出,例如:97->97
public class Demo04PrintStream {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("itcast\\src\\demo17Buffered\\a.txt");
ps.write(97);//a
ps.println(97);
ps.println(8.8);
ps.println('a');
ps.println("Hello World");
ps.println(true);
System.out.println("我在工作台输出");
System.setOut(ps);//把输出语句的目的地改为打印流的目的地
System.out.println("我在打印流的目的地输出");
ps.close();
}
}
TCP通信协议下客户端与服务器之间的通信
客户端
TCP通信的客户端:向服务器发送请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:
java.net.Socket:此类实现客户端套接字(也叫"套接字"),套接字是两台机器间通信的端点。
套接字:包含了IP地址和端口号的网络单位
构造方法:
Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号。
参数:
String host:服务器主机的名称/服务器的IP地址
int port:服务器的端口号
成员方法:
1.getInputStream() 返回此套接字的输入流。
2.OutputStream getOutputStream() 返回此套接字的输出流。
3.void close() 关闭此套接字
实现步骤:
1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
2.使用Socket对象中的方法getOutputStream()方法获取网络字节输出流OutputStream对象
3.使用网络字节输出流OutputStream对象的方法write,给服务器发送数据
4.使用Socket对象的方法getInputStream()获取网络字节输入流InputStream对象
5.使用InputStream的方法read,读取服务器回写的数据
6.释放资源
注意:
1.客户端和服务器进行交互,必须使用网络流Socket,不能用自己创建的流对象
2.当我们穿件服务器对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
这时如果服务器没有启动,那么就会抛出异常
如果服务器已经启动,那么就可以进行交互了
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
OutputStream os = socket.getOutputStream();
os.write("你好服务器".getBytes());
InputStream is = socket.getInputStream();
byte[] b = new byte[1024];
is.read(b);
System.out.println(new String(b));
os.close();
socket.close();
}
}
服务器端
TCP通信的服务器端:接受服务器的请求,给服务器写数据
服务器的实现步骤:
1.创建服务器ServerSocket对象和系统指定的端口
2.使用呢SerSocket对象中的方法accept,获取到请求的客户端对象Socket
3.使用Socket对象的方法getInputStream()获取网络字节输入流InputStream对象
4.使用InputStream的方法read,读取客户端传递的数据
5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
6.使用OutputStream对象中的方法write()给客户端回信息
7.释放资源
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket a = ss.accept();
InputStream is = a.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println(new String(b,0,len));
OutputStream os = a.getOutputStream();
os.write("服务器已收到".getBytes());
a.close();
ss.close();
}
}
综合案例:文件上传
文件上传的客户端
文件上传的服务器端
服务器端
public class Service {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
while(true){
/*
这里使用多线程提高效率,
有一个客户端上传文件,就开启一个线程,完成文件的上传
*/
new Thread(new Runnable() {
@Override
public void run() {
try{
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
File file =new File("D:\\upload");
if (!file.exists()){
file.mkdirs();
}
String FileName = "itcast"+System.currentTimeMillis()+ new Random().nextInt(9999)+".jpg";
FileOutputStream fos =new FileOutputStream(file+"\\"+FileName);
int len = 0;
byte[] b =new byte[1024];
while ((len = is.read(b))!=-1){
fos.write(b,0,len);
}
//is.read读取不到-1,因为不会传入数据中,read方法会进入到阻塞状态
//socket.shutdownInput()方法:read进入到阻塞状态则会是read方法结束
socket.shutdownInput();
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes());
fos.close();
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
//服务器在死循环,一直开着服务器,可以不用关闭
//ss.close();
}
}
客户端
public class Client {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("c:\\1.jpg");
Socket socket =new Socket("127.0.0.1",8888);
OutputStream os = socket.getOutputStream();
int len = 0;
byte[] b =new byte[1024];
while ((len= fis.read(b))!=-1){
os.write(b,0,len);
}
//fis.read读取不到-1,因为不会传入数据中,read方法会进入到阻塞状态
//socket.shutdownInput()方法:read进入到阻塞状态则会是read方法结束
/*
void shutdownOutput() 禁用此套接字的输出流。
上传完数据之后,主动给服务器一个结束标志
*/
socket.shutdownOutput();
len = 0;
InputStream is = socket.getInputStream();
while ((len = is.read(b))!=-1){
System.out.println(new String(b,0,len));
}
socket.shutdownInput();
System.out.println("1");
fis.close();
socket.close();
}
}
模拟B/S服务器
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
while(true){
new Thread(new Runnable() {
@Override
public void run() {
try{
Socket socket= server.accept();
InputStream is = socket.getInputStream();
//把网络字节输入流转换为字符缓冲输出流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//把客户端请求信息的第一行读取出来GET /11_NET/web/index.html
String line = br.readLine();
//切割读取信息的一部分/11_NET/web/index.html
String[] arr = line.split(" ");
//把路径前边的/去掉,进行截取11_NET/web/index.html
String htmlpath = arr[1].substring(1);
//创建一个本地的字节输入流,构造方法中绑定要读取的html路径
FileInputStream fis = new FileInputStream(htmlpath);
//使用Socket中的方法getOutputStream获取网络字节输出流OutputStream
OutputStream os = socket.getOutputStream();
//写入HTTP协议响应头,固定写法
os.write("HTTP/1.1.200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
os.write("\r\n".getBytes());
//一读一写复制文件,把服务器读取的html文件回写到客户端
int len = 0;
byte[] b =new byte[1024];
while ((len = fis.read(b))!=-1){
os.write(b,0,len);
}
//释放资源
fis.close();
socket.close();
server.close();
}
catch (IOException e){
System.out.println(e);
}
}
}).start();
}
}
}
函数式接口
函数式接口(FunctionalInterface):有且只用一个抽象方法,称之为函数式接口
当然接口中可以包含其他的方法(默认,静态,私有)
@Override注解
作用:检查方法是否为重写的方法
是:编译成功
否:编译失败
@FunctionalInterface注解
作用:可以检测接口是否是一个函数式接口(是否只有一个抽象方法
@FunctionalInterface//此注解可以确定该函数式接口只有一个抽象函数
public interface MyFunctionalInterface {
//定义一个抽象方法
public abstract void method();
}
函数式接口的使用
public class Demo {
public static void show(MyFunctionalInterface myinter){
myinter.method();
}
public static void main(String[] args) {
show(new MyFunctionalInterefaceImpl());
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类重写接口中的抽象方法");
}
});
show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}
}
函数式接口作为参数的案例
例如java.lang.Runnable接口就是一个函数式接口
假设有一个startThread方法使用该接口作为参数,那么可以使用Lambda进行传参
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别
public class Demo01Runnable {
public static void startThread(Runnable run){
new Thread(run).start();
}
public static void main(String[] args) {
startThread(()-> System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了")
);
}
函数式接口作为方法的返回值
public class Demo02Comparator {
public static Comparator<String> getCompararor(){
return (o1,o2)-> o2.length()-o1.length();
}
public static void main(String[] args) {
String[] arr = {"aaa","b","ccccc","dddddddddddddddddd"};
System.out.println(Arrays.toString(arr));
Arrays.sort(arr, getCompararor());
System.out.println(Arrays.toString(arr));;
/*
[aaa, b, ccccc, dddddddddddddddddd]
[dddddddddddddddddd, ccccc, aaa, b]
*/
}
}
常用的函数式接口supplier(供给者)
/*
java.util.function.Supplier<T>接口仅包含一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据。
Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的方法get就会生产什么类型的数据
*/
public class Demo01Supplier {
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
String s = getString(()->{return "胡歌";});
System.out.println(s);
}
}
常用的函数式接口Consumer(消费者)
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用该数据),需要自定义(输出,计算...)
public class Demo02Consumer {
public static void method(String name, Consumer<String> con){
con.accept(name);
}
//调用method方法,消费数据"赵丽颖",怎么消费看自定义
public static void main(String[] args) {
method("赵丽颖",(String name)->{
//System.out.println(name);
//消费方式:把字符串反转
String reName = new StringBuffer(name).reverse().toString();
System.out.println(reName);
});
}
}
Consumer接口的默认方法AndThen
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,对数据进行消费
Consumer<String> con1
Consumer<String> con1
con1.andThen(cn2).accept(s);谁写前边谁先消费
public class Demo03AndThen {
public static void method(String s, Consumer<String> con1, Consumer<String> con2){
//con1.accept(s);
//con2.accept(s);
con1.andThen(con2).accept(s);
}
public static void main(String[] args) {
//用t来接收"Hello"
method("Hello",(t)->{
System.out.println(t.toUpperCase());
},(t)->{
System.out.println(t.toLowerCase());
});
}
}
格式化打印信息
public class DemoConsumer {
public static void main(String[] args) {
String[] arr ={"迪丽热巴,女","古力娜扎,女","马尔扎哈,男"};
printInfo(arr,(m)->{
//对message进行分割,分割后返回的是一个数组
System.out.println(m.split(",")[0]);},(m)->{
System.out.println(m.split(",")[1]);
});
}
public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String>con2){
for (String message : arr) {
con1.andThen(con2).accept(message);
}
}
}
/*
迪丽热巴
女
古力娜扎
女
马尔扎哈
男
*/
函数式接口predicate接口(对数据进行判断)
java.util.function.predicate<T>接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值
predicate接口中包含一个抽象方法:
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果:
符合条件,返回true
public class Demo02Predicate {
public static void main(String[] args) {
String s ="avcde";
//用str接收s,str是在函数式接口内运行的
boolean b = checkString(s,str ->
str.length() > 5
);
System.out.println(b);
}
public static boolean checkString(String s , Predicate<String> pre){
return pre.test(s);
}
用predicate接口中的and方法实现逻辑与操作
一个判断字符串的长度是否大于5
一个判断字符串是否包含a
两个必须同时满足
public class Demo03Predicate {
public static void main(String[] args) {
/*
一个判断字符串的长度是否大于5
一个判断字符串是否包含a
两个必须同时满足
*/
String str = "abcdef";
boolean a = checkString(str, (String s) -> {
return s.length() > 5;
}, (String s) -> {
return s.contains("a");
});
System.out.println(a);
}
public static boolean checkString(String s , Predicate<String>pre1,Predicate<String>pre2){
return pre1.test(s)&& pre1.test(s);
return pre1.and(pre2).test(s);//与上一句等同
}
}
用predicate接口中的or方法和negate方法实现逻辑或操作和逻辑非
public class Demo03Predicate {
public static void main(String[] args) {
/*
一个判断字符串的长度是否大于5
一个判断字符串是否包含a
两个必须同时满足
*/
String str = "abc";
boolean a = checkString(str, (String s) -> {
return s.length() > 5;
}, (String s) -> {
return s.contains("a");
});
System.out.println(a);
}
public static boolean checkString(String s , Predicate<String>pre1,Predicate<String>pre2){
//return !pre1.test(s);
return pre1.negate().test(s);
//return pre1.test(s)||pre2.test(s);
//return pre1.or(pre2).test(s);//与上一句等同
}
}
练习:集合信息筛选
练习:集合信息筛选
筛选数组中满足一下两个条件的的信息:
1.必须是女生
2.姓名为4个字
public class Demo05Test {
public static void main(String[] args) {
String[] arr = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"};
ArrayList<String> list = checkMessage(arr, (String s) -> {
//条件1,必须包含女
return s.split(",")[1].contains("女");
}, (String s) -> {
//条件2,名字必须为4个字
return s.split(",")[0].length() == 4;
});
System.out.println(list);
}
public static ArrayList<String> checkMessage(String[] arr, Predicate<String> pre1,Predicate<String> pre2){
//两个测试用函数式接口同时对数据arr进行测试
ArrayList<String> list = new ArrayList<>();
for (String s : arr) {
boolean b = pre1.and(pre2).test(s);
if(b){
list.add(s);
}
}
return list;
}
}
Function接口
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
前者称之为前置条件,后者称之为后置条件
Function接口中主要的抽象方法为 R apply(T t)根据类型T的参数获取类型R的结果
例如:将String类型转换为Integer类型
public class Demo01Function {
public static void main(String[] args) {
String s = "12345";
String s2 ="3654";
change(s,s2,(String str)->{
return Integer.parseInt(str);
});
}
public static void change(String s ,String s2, Function<String,Integer> fun){
Integer in1 = fun.apply(s);
Integer in2 = fun.apply(s2);
System.out.println(in1);
System.out.println(in2);
}
}
之所以用函数式接口,看似多此一举,实际上因为是lambda表达式,参数可以使未知数,可以是传递进来的数据中的任何一个,因此可以不用反复写函数体,只需写一遍,然后反复调用即可
Function接口中的andThen方法(可以把两个接口连接在一起)
用Function接口中的默认方法andThen:进行组合操作
需求:
把String类型的"123",转换为Integer类型,把转换后的数据加10
然后在转换为String类型
public class Demo04Function_andThen {
public static void main(String[] args) {
String s ="123";
change(s,(String s1)->{
int i = Integer.parseInt(s1);
int i1 = i + 10;
return i1;
},(Integer i1)->{
//Function接口必有返回值
String s1 = i1.toString();
return s1;
});
//简化写法
// change(s,s1->Integer.parseInt(s1)+10,i1->i1+"");
}
public static void change(String str, Function<String,Integer> fun1,Function<Integer,String> fun2){
//Function接口返回的数值在这里
String s = fun1.andThen(fun2).apply(str);
System.out.println(s);
}
}
练习:Function进行函数模型拼接
练习:自定义函数模型拼接
题目
请使用Function进行函数模型拼接,按照顺序需要执行的多个函数操作为:
String str = "赵丽颖,20";
1.将字符串截取数字年龄部分,得到字符串;
2.将上一步的字符串转换为int类型的数字;
3.将上一步的int数字累加100,得到结果int数字
public class Demo06Test {
public static void main(String[] args) {
String str = "赵丽颖,20";
change(str,(String s1)->{
String s = s1.split(",")[1];
return s;
},(String s2)->{
return Integer.parseInt(s2)+100;
});
}
public static void change(String s, Function<String,String> fun1, Function<String,Integer> fun2){
Integer i = fun1.andThen(fun2).apply(s);
System.out.println(i);
}
}
stream方法对数据进行遍历筛选操作
public class Demo02StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("阿朱");
list.add("张三丰");
//用list集合中的stream方法对数据进行遍历筛选操作
list.stream()
//stream流中有filter方法,内置predicate接口可以对数据进行判断
.filter(name->name.startsWith("张"))
.filter(name->name.length()==3)
//stream流中有方法forEach方法,内置Consumer接口可以对数据进行消费
.forEach(name-> System.out.println(name));
}
}
流式思想
流在一个步骤中使用完,会生成新的流进入下一个步骤,不会在本步骤停留
(重要) filter过滤流
limit只要流数据前n个
skip跳过前n个数据
map把数据映射为另一种数据
forEach消费数据
将集合和数组转换为Stream流
java,util.stream.Stream<T>是新加入到额最常用的流接口(这并不是函数式接口)
获取流的方式:
-所有的Collection集合都可以通过stream默认方法获取流;
default Stream<E> stream()
-Stream接口的静态方法of可以获取数组对应的流
static<T> Stream<T> of {T... values}
参数是一个可变参数,那么我们就可以传递一个数组
public class Demo03GetStream {
public static void main(String[] args) {
//将各类集合转换为Stream流
List<String> list =new ArrayList<>();
Stream<String> stream1 =list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream5 = set.stream();
HashMap<String, String> map = new HashMap<>();
Set<String> keyset = map.keySet();
Stream<String> stream2 = keyset.stream();
Collection<String> values = map.values();
Stream<String> stream3 = values.stream();
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream4 = entries.stream();
//将数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1,2,3,4,5);
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
}
}
Stream常用方法
public class Demo04ideal {
public static void main(String[] args) {
Stream<String> s = Stream.of("张三", "李四", "王五", "赵六", "田七");
//stream流调用forEach方法,会将stream中的数据一个个传递入consumer接口中消费
s.forEach((String name)->{
System.out.println(name);
});
}
}
Stream流中的filter(过滤)方法
Stream流中的常用方法filter:用于对stream流中的数据进行过滤
Stream<T> filter(Predicate<? super> predicate);
filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
predicate中的抽象方法:
boolean test(T t);
public class Demo03Stream_filter {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("张三丰","赵敏","张翠山","周芷若","张无忌");
Stream<String> stream2 = stream1.filter((String name) -> {
return name.startsWith("张");
});
stream2.forEach((String name)->{
System.out.println(name);
});
}
}
Sream流的特点:Stream流只能消费一次,第一个Stream流调用完毕方法,数据就会流转到下一个Stream中,而第一个Stream流就会关闭
这时候在遍历stream1就会出错
映射:使用map方法
如果需要将流中的元素映射到另一个流中,可以使用map方法.
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
Function中的抽象方法:
R apply(T t);
public class Demo04map {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1","2","3","4");
Stream<Integer> stream1 = stream.map((String s) -> {
return Integer.parseInt(s);
});
stream1.forEach(s-> System.out.println(s));
}
}
Stream流中的count方法(计算stream流中元素个数)
long count();
count方法是一个终结方法,返回值是一个long类型的整数
所以不能再调用stream中的其他方法
public class Demo05count {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
Stream<Integer> stream1 = list.stream();
//count是终结方法,使用后不返回一个流
long c = stream1.count();
System.out.println(c);
}
}
Stream流中的limit方法
是一个延迟方法,只是对流中的数据进行截取,返回的是一个新的流,可以继续使用Stream流中的其他方法
public class Demo06limit {
public static void main(String[] args) {
String[] arr =new String[]{"喜洋洋","美羊羊","懒洋洋","沸羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
Stream<String> stream2 = stream.limit(3);
stream2.forEach(s-> System.out.println(s));
}
}
Stream中的skip(跳过)
Stream流中的concat方法(组合)
public class demo07concat {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("喜洋洋", "美羊羊", "懒洋洋", "沸羊羊", "灰太狼", "红太狼");
Stream<String> stream2 = Stream.of("张三丰", "赵敏", "张翠山", "周芷若", "张无忌");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(s-> System.out.println(s));
}
}
//会将两个数组组合在一起
测试信息筛选
public class StreamTest {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("迪丽热巴");
list1.add("宋远桥");
list1.add("苏星河");
list1.add("石破天");
list1.add("石中玉");
list1.add("老子");
list1.add("庄子");
list1.add("洪七公");
ArrayList<String> list2 =new ArrayList<>();
list2.add("古力娜扎");
list2.add("张无忌");
list2.add("赵丽颖");
list2.add("张三丰");
list2.add("尼古拉斯赵四");
list2.add("张天爱");
list2.add("张三狗");
ArrayList<String> one = new ArrayList<>();
for (String s : list1) {
if(s.length()==3){
one.add(s);
}
}
ArrayList<String> one1 =new ArrayList<>();
for (int i = 0; i < 3; i++) {
one1.add(one.get(i));
}
ArrayList<String> two =new ArrayList<>();
for (String s : list2) {
if(s.startsWith("张")){
two.add(s);
}
}
ArrayList<String> two1 = new ArrayList<>();
for (int i = 2; i < two.size(); i++) {
two1.add(two.get(i));
}
ArrayList<String> all = new ArrayList<>();
all.addAll(one1);
all.addAll(two1);
ArrayList<Person> person =new ArrayList<>();
for (String s : all) {
person.add(new Person(s));
}
System.out.println(person);
}
}
用Stream流对测试数据筛选
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("迪丽热巴");
list1.add("宋远桥");
list1.add("苏星河");
list1.add("石破天");
list1.add("石中玉");
list1.add("老子");
list1.add("庄子");
list1.add("洪七公");
//1.第一个集合只要名字为三个字的成员并且只要前三个
/*
filter过滤流
limit限制流个数
skip跳过前n个数据
map把数据映射为另一种数据
forEach消费数据
*/
Stream<String> list1Stream = list1.stream().filter(s -> s.length() == 3).limit(3);
ArrayList<String> list2 =new ArrayList<>();
list2.add("古力娜扎");
list2.add("张无忌");
list2.add("赵丽颖");
list2.add("张三丰");
list2.add("尼古拉斯赵四");
list2.add("张天爱");
list2.add("张三狗");
//第二个集合只要型张的成员并且不要前两个
Stream<String> list2Stream = list2.stream().filter(s -> s.startsWith("张")).skip(2);
// 将两个集合流合并为一个集合流,并且以此创建Person对象,并遍历
Stream.concat(list1Stream,list2Stream).map(s->new Person(s)).forEach(s-> System.out.println(s));
}
}
方法应用
public class Demo02ObjectReference{
public static void printString(Printable p){
p.print("Hello");
}
public static void main(String[] args) {
//printString(s-> System.out.println(s));
printString((s)->{
MethodRerObject obj = new MethodRerObject();
//调用MethodRerObject对象中的prinyUpperCaseString方法
obj.prinyUpperCaseString(s);
});
//采用方法引用(采用obj对象中的方法prinyUpperCaseString来对重写p中的print方法)
MethodRerObject obj = new MethodRerObject();
printString(obj::prinyUpperCaseString);
}
}
通过类名引用静态成员方法
通过类名引用静态成员方法
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法
public class demo01StaticMethodReference {
public static int method(int number,Calcabel c){
return c.calsAbs(number);
}
public static void main(String[] args) {
int m = method(-4, (int num) -> {
return Math.abs(num);
});
System.out.println(m);
//使用方法引用优化Lambda表达式
/*
Math类是存在的
abs计算绝对值的静态方法也是存在的
所以我们可以直接通过类名引用静态方法
*/
method(-10,Math::abs);
}
}
通过父类super引用成员方法
public class Human {
public void sayHello(){
System.out.println("Hello 我是Human!");
}
}
@FunctionalInterface
public interface Greetable {
//定义一个见面的方法
void greet();
}
public class Man extends Human{
@Override
public void sayHello() {
System.out.println("Hello 我是Man");
}
public void method(Greetable g){
g.greet();
}
//因为有子父类关系,所以存在一个关键字super,代表父类,所以我们可以直接使用super调用父类的方法
public void show(){
method(()->{
/*Human h = new Human();
h.sayHello();*/
super.sayHello();
});
/*
因为super已经存在了
sayHello方法也已经存在了
方法引用是为了简化Lambda表达式
*/
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();//Hello 我是Human!
new Man().sayHello();//Hello 我是Man
}
}
通过this引用本类的成员方法
函数式接口
@FunctionalInterface
public interface Richable {
void buy();
}
实现类
/*
通过this引用本类的成员方法
*/
public class Husband {
//定义一个买房子的方法
public void buyHouse(){
System.out.println("北京二环内买一套四合院");
}
//定义一个结婚的方法,参数传递Richable接口
public void marry(Richable r){
r.buy();
}
public void soHappy(){
marry(()->{
this.buyHouse();
});
/*
使用方法引用优化Lambda表达式
this是已经存在的
本类的成员方法buyHouse也是已经存在的
所有我们可以直接使用this引用本类的成员方法buyHouse
*/
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
类的构造方法引用
函数式接口
public interface PersonBuilder {
Person builderPerson(String name);
}
实现l类
/*
类的构造器(构造方法)引用
*/
public class demo {
//定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name,PersonBuilder pb){
Person person = pb.builderPerson(name);
System.out.println(person.getName());
}
public static void main(String[] args) {
printName("迪丽热巴",(String name)->{
return new Person(name);
});
/*
使用方法引用优化Lambda表达式
构造方法new Person(String name)已知
创建对象已知 new
就可以使用Peron引用new创建对象
*/
printName("古力娜扎",Person::new);
}
}