Java语法基础

bilibili视频参考

1.Java基础

1.1hello world

  • java文件的名称必须和class名称保持一致
  • 一个Java文件中可以包含多个class但是public class只能有一个
  • public static void main(String[] args)public class Hello是所有java程序的入口,如果想执行对应的java代码,必须添加如下方法
  • main函数方法中参数列表可以有多种写法:String[] args, String [] args, String args[]
  • (args参数名称无所谓,一般写成args)
public class Hello
{
    public static void main(String[] args)
    {
        System.out.println("hello world!");
    }
}
class Test
{
    public static void main(String[] args)
    {
        System.out.println("I am a test!")
    }
}
System.out.println(~4);
//-5
/*
原码:如果机器字长为n,那么一个数的原码就是用一个n位的二进制数,其中最高位为符号位:正数为0,负数为1。剩下的n-1位表示概数的绝对值。
反码:反码就是在原码的基础上,符号位不变其他位按位取反(就是0变1,1变0)就可以了。
补码:在反码的基础上按照正常的加法运算加1。
移码:不管正负数,只要将其补码的符号位取反即可。
*/

1.2Java中的static

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念。

被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区或者方法区内找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。

static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用),但是不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。

static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:

类名.静态方法名(参数列表…)

类名.静态变量名

用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块(用处非常大)。

static变量

按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:

对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。

对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

静态方法

静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联!

因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。

1.3面向对象

  • package
    • 作用

      区分相同名字的类

      当类很多时,可以有效果管理类

      控制访问范围

    • 同一个包中的类直接调用,无需import导入。包名全部小写

    • 访问控制修饰符

      公开级别:public 对外公开

      受保护级别:protected 对子类和同一个包中的类公开

      默认界别:用friendly或者缺省 向同一个包中的类公开

      私有级别:private 只有类本身可以访问,不对外公开

修饰符同一个类同一个包子类其他包
private
default
protected
public
  • 继承

    Java中所有的类都是Object类的子类

    继承的规则:子类继承父类所有(可见)属性和所有(可见)方法,但是不继承构造方法

    实例化过程:先父类,然后子类

    成员方法的覆盖:名称、参数、返回值和父类方法的成员方法一样

    覆盖规则:

    • 在父子类之间继承时发生
    • 多个方法的名称相同
    • 返回值类型必须相同
    • 每个方法参数数量和参数类型和顺序相同
    • 权限修饰符要求:子类方法的要不小于父类方法的
    • 子类方法只能抛出父类方法异常或其异常的子类
  • super和this关键字

    super:指向父类的引用

    this:指向本类的引用

    • super()

      作用:调用父类的构造器

      只能出现在子类的构造器中,且必须是第一行

      super()中的参数,决定了调用父类哪个构造器

      如果子类构造器中没有出现super(),那么默认给增加super(),即调用父类的空构造器

    • this()

      作用:调用本类的构造器

      只能写在构造器的第一行

      在同一个构造器中super()和this()不能同时出现

    • final可以修饰的元素】

      变量(属性和局部变量):不能被重新赋值

      • 在声明的时候同时赋值
      • 在构造器中赋值

      方法:不能被覆盖,即不能被修改

      类:不能被继承

  • 抽象类

    抽象方法:只有方法申明,没有方法实现的方法。一般用abstract申明,以“;”结尾,例如:public abstract void getArea();

    抽象类:含有抽象方法的类必须声明为抽象类,用abstract声明class。

    注意点:

    • 抽象类不能被实例化
    • 抽象类可以没有抽象方法
    • 一旦包含了抽象方法,则这个类必须声明为abstract类,并且抽象方法必须在子类中被实现,否则子类只能声明为abstract类。
    • 抽象方法不能为static
  • 接口

    接口不是一个类,不能实例化;

    接口是常量和抽象方法的集合

    接口语法:访问权限控制符 interface 接口名

    ​ {

    ​ 属性;

    ​ 方法;

    ​ }

    接口的特点:

    • 接口使用interface关键字来定义,而不是class
    • 接口中定义的变量都是公共静态最终变量。访问形式:接口名/类名/对象名.变量名
    • 接口中没有自己的构造函数,而且接口中定义的方法全部都是抽象方法,即只提供方法的定义,而没有提供方法的具体实现语句。
    • 接口不能继承其他类,但是可以继承其他接口。
  • 接口和抽象类的区别

    接口不能含有任何非抽象方法(jdk1.8之后就可以有一个default方法),而抽象类可以。

    类可以实现许多接口,但只能有一个父类。

    接口不是类分级结构的一部分,没有联系的类可以实现相同的接口

  • 静态修饰符static

    • static可以修饰类的成员方法、类的成员变量

    • 注意:static不能修饰局部变量

    • 静态属性

      序号静态属性非静态属性
      1多个对象共享一个属性每个对象共享一个属性
      2该属性属于类该属性属于每个实例
      3类变量实例变量
      4对象.属性/类.属性对象.属性
    • 静态属性在该类第一次被加载到虚拟机时,分配静态存储区,以后每次运行不在分配空间。

    • 限制

      • 静态方法可以直接访问类中其他静态成员
      • 如果静态方法中要访问类中的非静态成员,必须先实例化类
      • 静态方法中不能使用this
      • 静态方法不能被非静态方法覆盖
  • 包装类

    Integer类构造方法有两种:

    • 以Int型变量作为参数创建Integer对象:Integer number=new Integer(1);
    • 以String类型变量作为参数Integer对象:Integer number=new Integer(“1”);

    Integer类常用的方法:

    Object.equals(Object IntegerObj)//比较此对象与指定的对象是否相等
    
    Object.toString()//返回一个表示该Integer值的String对象
    parseInt(String str)//返回包含在由str指定的字符串中的数字的等价整数值
    

1.4Java常用类

1.4.1Object类介绍

Object类是所有类的父类,一个类都会直接或者间接继承自该类

  • toString()方法

    作用:打印对象的信息(重写前:包名类名@地址值 重写后:对象中的属性值)

  • equals()方法

    作用:用来比较两个对象(重写前:比较对象的地址值 重写后:比较对象中的属性值)

1.4.2Objects类

  • equals()方法
public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));}

作用:比较两个对象是否相同,添加了一些健壮性判断

1.4.3日期相关类

1.Date类

  • 构造方法

    Date():根据当前系统时间创建日期对象

    Date(long time):根据传入的毫秒值时间创建日期对象

  • 成员方法

    long getTime():获取当前日期对象的毫秒值时间

    String toLocaleString():根据本地格式转换日期对象

2.DateFormat类和SimpleDateFormat类

  • 构造方法

    SimpleDateFormat(String s):根据指定模板创建日期格式化对象

  • 成员方法

    String format(Date date):根据指定格式格式化日期对象

    Date parse(String s):根据指定格式解析字符串

3.Calendar类

  • 创建对象方式

    Calendar c=Calendar.getInstance();//获取日历类对象

  • 成员方法

    int get(int field);//获取指定日历字段信息

    void set(int field,int value);//将指定日历字段设置为指定的值

    void add(int field,int amount);//将指定日历字段增加或者减少指定的值

1.4.4System类

java.lang.System类中提供了大量的静态方法,可以获取系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间
  • public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length):将数组中指定的数据拷贝到另一个数组中

1.4.5StringBuilder类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1MlJ54F-1595949513967)(G:\图片\Java\Snipaste_2020-07-10_20-43-25.jpg)]

  • 构造函数

    public StringBuilder():构造一个空的StringBuilder容器

    public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去

  • 常用成员方法

    public StringBuilder append(……):添加任意类型数据的字符串形式,并返回当前对象自身

    public String toString():将当前StringBuilder对象转换成String对象

1.4.6包装类

基本类型对应的包装类(位于java.lang包中)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

装箱与拆箱

  • 装箱:从基本数据类型转换为对应的包装类对象

    构造方法:

    ​ Integer(int value):构造一个新分配的Integer对象,表示指定的int值

    ​ Integer(String s):构造一个新分配的Integer对象,它表示String参数所指示的int值。传递的字符串,必须是基本类型的字符串,否则就会抛出异常,例如:“100”正确,而“hfh"会抛出异常

    静态方法:

    ​ static Integer valueOf(int i):返回一个表示指定int值的Integer实例。

    ​ static Integer valueOf(String s):返回保存指定的String的值的Integer对象。

  • 拆箱:从包装类对象转换为对应的基本数据类型

    成员方法:

    ​ int intValue():以int类型返回该Integer的值。

    //基本数据类型->包装对象
    Integer i=new Integer(10);//使用构造函数
    Integer j=Integer.valueOf(10);//使用包装类中的valueOf方法
    //包装对象->基本数据类型
    int num=i.intValue();
    
  • 自动装箱与拆箱

    ​ 自动装箱:直接把int类型的整数赋值给包装类

    ​ Integer in=1;等价于Integer in=new Integer(1);

    ​ 自动拆箱:in是包装类,无法直接参与运算,可以自动转换为基本类型的数据,再参与运算

    ​ in=in+2;相当于in.intValue()+2=3;再将结果自动装箱

    ArrayList<Integer> list=new ArrayList<>();
    list.add(1);//自动装箱
    int a=list.get(0);//自动拆箱
    

基本类型与字符串之间的转换

基本类型转换为String(假设a是一个integer类型的数据)

  • a.toString()

  • String.valueOf(a)

  • a+""

    运行效率逐渐变低

    toString()方法可以直接调用进行转换

    String.valueOf(a)方法底层调用了Integer.toString()方法,但是会在调用前做空判

    a+""底层使用了StringBuilder实现,先用append方法拼接,再利用toString()方法获取字符串

String转换成基本数据类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串转换成为对应的基本类型:

- public static byte parseByte(String s)
- public static short parseShort(String s)
- public static int parseInt(String s)
- public static long parseLong(String s)
- public static float parseFloat(String s)
- public static double parseDouble(String s)
- public static boolean parseBoolean(String s)

1.5Collection集合

1…5.1集合概述

**集合:**是Java中提供的一种容器,可以用来存储多个数据

集合和数组都是容器,它们的区别是什么?

  • 数组长度是固定的,集合的长度是可变的
  • 数组中存储的都是同一类型的元素,可以存储基本数据类型。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-az4vgS9z-1595949513971)(G:\Snipaste_pic\Snipaste_2020-07-11_12-46-00.png)]

Collection集合的常用方法

boolean add(E e):向集合中添加元素

boolean remove(E e):删除集合中的某个元素

void clear():清空集合所有元素

boolean contains(E e):判断集合中是否包含某个元素

boolean isEmpty():判断集合是否为空

int size():获取集合的长度

Object[] toArray():将集合转换成一个数组

1.5.2Iterator迭代器

**迭代:**在取元素之前首先要判断集合中有没有元素,如果有就把这个元素取出来,继续判断

方法:

​ public E next():返回迭代的下一个元素

​ public boolean hasNext():如果仍然有元素可以迭代,则返回true

1.5.3泛型的使用

  • 泛型类和泛型方法
  • 泛型接口和泛型方法
  • 泛型通配符

1.6List接口

  • ArrayList:底层是一个数组,查询快,增删慢
/*
* java.util.List接口 extends Collection接口
* List接口的特点:
*   1.有序集合,存储元素和取出元素的顺序是一致的
*   2.有索引,包含一些带索引的方法
*   3.允许存储重复的元素
* List接口中带索引的方法(特有):
*   public void add(int index,E element):将指定的元素添加到该集合中指定的位置上
*   public E get(int index):返回集合中指定位置的元素
*   public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素
*   public E set(int index,E element):用指定的元素替换集合中指定位置的元素,返回值的更新前的元素
* Attention:操作索引要防止索引越界
*/
  • LinkedList:底层是一个链表,增删快,查询慢
/*
* java.util.LinkedList集合 implements List接口
* 特点:
*   1.底层是一个链表结构,查询快,增删慢
*   2.里面包含了大量操作首尾元素的方法
*   Attention:使用LinkedList集合特有的方法不能使用多态
* 
*   public void addFirst(E e):将指定元素插入此列表的开头
*   public void addLast(E e):将指定元素添加到此类表的结尾
*   public void push(E e):将元素推进此列表所表示的堆栈
*
*   public E getFirst():返回列表的第一个元素
*   public E getLast():返回列表的最后一个元素
* 
*   public E removeFirst():移除并返回此列表的第一个元素
*   public E removeLast():移除并返回此列表的最后一个元素
*   public E pop():从此列表所表示的堆栈处弹出一个元素
*
*   public boolean isEmpty():如果列表不包含元素,则返回true
*/
  • Vector:底层和ArrayList一样是一个数组,version1.2之后被ArrayList取代,单线程的,速度较慢

1.7set集合

  • HashSet
/*
* java.util.Set接口 extends Collection接口
* Set接口的特点:
*   1.不允许存储重复的元素
*   2.没有索引,不能使用for循环遍历集合
* java.util.HashSet集合 implements Set接口
* HashSet特点:
*   1.不允许存储重复的值
*   2.没有索引,不能使用for循环遍历集合
*   3.是一个无序的集合,存储元素和取出元素的顺序可能不一样
*   4.底层是一个哈希表结构(查询速度非常快)
*/
  • LinkedHashSet
* java.util.LinkedHashSet集合 extends HashSet集合
* LinkedHashSet集合特点:
* 底层是一个哈希表(数组+链表/红黑树)+链表:多了一个链表(记录元素的存储顺序,保证元素有序)

1.8Collections

/*
    java.util.Collections是集合工具类,用来对集合进行操作。部分方法如下:
        public static <T> boolean addAll(Collections<T> c,T…… elements):往集合中添加一些元素
        public static void shuffle(List<?> list):打乱集合顺序
        public static <T> void sort(List<T> list):将集合中元素按照默认规则排序
            Attention:sort(List<T> list)使用的前提:
            被排序的集合里面存储的元素,必须实现Comparable接口中的compareTo()
        public static <T> void sort(List<T> list,Comparator<? super T>):将集合中的元素按指定规则排序
            Comparator和Comparable的区别:
                Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo()
                Comparator:相当于找一个第三方的裁判,比较两个
 */

1.9Map

/*
    java.util.Map<k,v>集合
    Map集合的特点:
        1.Map集合是一个双列集合,一个元素包含两个值(key,value)
        2.Map集合中的元素,key和value数据类型可以不同
        3.Map集合中的元素,key不可以重复,value可以重复
        4.Map集合中的元素,key和value是一一对应的
    java.util.HashMap<k,v>集合 implements Map<k,v>接口
    HashMap集合的特点:
        1.HashMap底层是哈希表,查询速度快
            JDK1.8之前:数组+单链表
            JDK1.8之后:数组+单链表/红黑树
        2.HashMap集合是一个无序的集合,存储元素和取出元素有可能不一致
    java.util.LinkedHashMap<k,v>集合 implements Map<k,v>接口
    LinkedHashMap特点:
        1.LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序)
        2。LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一样的
    Map接口中定义的常用方法:
        public V put(K key,V value):把指定的键值对添加到Map集合中
        public V remove(Object key):把指定的键所对应的键值对从Map集合中删除,返回删除元素的值
        public V get(Object key):根据指定的键,在Map集合中获取对应的值
        boolean containsKey(Object key):判断集合中是否包含指定的键
        public Set<K> keySet():获取Map集合中所有的键,存储到Set集合中去
        public Set<Map.Entry<K,V>> entrySet():获取到Map集合中所有的键值对对象的Set集合
 */

遍历

/*
    Map集合的第一种遍历方式:通过键找值的方式
    Map集合中的方法:
        Set<K> keySet():返回此映射中包含的键的Set视图
    实现步骤:
        1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到Set集合
        2.遍历Set集合,获取Map集合中的每一个key
        3.通过Map集合中的方法get(key)获取对应的value
 */
/*
    Map集合遍历的第二种方式:使用Entry对象遍历
    Map集合中的方法:
        Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的Set视图
    实现步骤:
        1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
        2.遍历Set集合,获取每一个Entry对象
        3.使用Entry对象中的方法getKey()和getValue()获取键与值
 */

Hashtable集合

/*
    java.util.Hashtable<K,V>集合 implements Map<K,V>接口
    Hashtable:底层也是一个哈希表,是一个线程安全的集合,单线程集合,速度慢
    HashMap:底层是一个哈希表,线程不安全,多线程集合,速度快

    HashMap集合(之前所有集合):可以存储null值,null键
    Hashtable集合,不能存储null键,null值

    Hashtable和Vector集合一样,在JDK1.2版本之后被更先进的集合(HashMap,ArrayList)取代了
    Hashtable的子类Properties依然在被使用
    Properties集合是一个唯一的和IO流相结合的集合
 */

1.10debug

f8:逐行执行程序

f7:进入到方法中

shift+f8:跳出方法

f9:跳到下一个断点,如果没有就结束程序

ctrl+f2:退出debug模式

console:切换到控制台

1.11异常

异常产生的分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1cxIVjg-1595949513975)(G:\Snipaste_pic\Snipaste_2020-07-15_22-42-23.png)]

Objects非空判断

该类由一些静态的实用方法组成,这些方法是空指针安全的,在源码中,对对象为null的值进行了抛出异常操作

public static T requireNonNull(T object):查看指定引用对象是不是null

public static <T> T requireNonNull(T object)
{
	if(obj==null)
	{
		throw new NullPointException();
	}
	return obj;
}

声明异常throws

声明异常:将问题标识出来,报告给调用者。如果方法内通过了throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理

关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。

声明异常的格式:

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2……{}

Throwable类中定义了3个异常处理的方法:

String getMessage():返回此throwable的简短描述
String toString():返回此throwable的详细消息字符串
void printStackTrace():JVM打印异常对象,默认方法,打印的异常信息是最全面的

1.12多线程

线程调度

  • 分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU时间
  • 抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级别相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度

创建线程类

java.lang.Thread 类代表线程,所有线程对象都必须是Thread类或者其子类的实例。

步骤:

1.定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

2.创建Thread子类的实例,即创建了线程的对象

3.调用线程对象的start()方法来启动线程

创建新线程有两种方式:

一种方法是将类声明为Thread的子类。该类应重写Thread类的run()方法。接下来可以分配并启动该子类的实例

class PrimeThraed extends Thread{
	long minPrime;
	PrimeThread(long minPrime)
	{
		this.minPrime=minPrime;
	}
	public void run()
	{
		……
	}
}
PrimeThread p=new PrimeThread(111);
p.start();

多线程的内存图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0YtiRrd-1595949513982)(G:\Snipaste_pic\Snipaste_2020-07-17_08-53-54.png)]

另一种方法是声明实现Runnable接口的类。该类然后实现run()方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。

class PrimeRun implements Runnable
{
    long minPrime;
    PrimeRum(long minPrime)
    {
        this.minPrime=minPrimr;
    }
    public void run()
    {
        ……
    }
}
//启动线程
PrimeRun p=new PrimeRun(111);
new Thread(p).start();

Thread类

构造函数:

  • public Thread():分配一个新的线程对象
  • public Thread(String name):分配一个指定名字的新的线程对象
  • public Thread(Runnable target):分配一个带有指定目标的新的线程对象
  • public Thread(Runnable target,String name):分配一个带有指定目标的新的线程对象并指定名字

常用方法:

  • public String getName():获取当前线程名称
  • public void start():导致此线程开始执行;JVM调用此线程的run方法
  • public void run():此线程要执行的任务在此处定义
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用

多线程安全问题

public class RunnableImpl implements Runnable{
    //定义一个多线程共享资源
    private int ticket=100;
    //设置线程任务:卖票
    @Override
    public void run() {
        while(true)
        {
            if(ticket>0)
            {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

public class TicketDemo1 {
    public static void main(String[] args) {
        RunnableImpl r=new RunnableImpl();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
/*
……
Thread-2正在卖第6张票
Thread-0正在卖第5张票
Thread-1正在卖第4张票
Thread-2正在卖第3张票
Thread-0正在卖第2张票
Thread-1正在卖第1张票
Thread-2正在卖第0张票
Thread-0正在卖第-1张票
/*

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-syVOwinI-1595949513984)(G:\Snipaste_pic\Snipaste_2020-07-17_23-44-20.png)]

线程同步

有三种方式完成同步操作:

  • 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

    格式:

    synchronized(同步锁)
    {
        需要同步操作的代码
    }
    

    同步锁:

    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

    1.锁对象可以是任意类型

    2.多个线程对象要使用同一把锁

    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外面等待(Blocked)

    原理:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzQdyFJ3-1595949513986)(G:\Snipaste_pic\Snipaste_2020-07-18_00-17-42.png)]

  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行方法的时候,其他线程只能在方法外等着。

    格式:

    public synchronized void method()
    {
        可能会产生线程安全问题的代码
    }
    

    同步锁:

    • 对于非static方法,同步锁就是this

    • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)

  • 锁机制

/*
    java.util.concurrent.locks.Lock接口
    Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
    Lock接口中的方法:
        void lock()获取锁
        void unlock()释放锁
    java.util.concurrent.locks.ReentrantLock implements Lock接口
    
    使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能出现线程安全的代码前面调用Lock接口中的方法lock获得锁
        3.在可能出现线程安全的代码后面调用Lock接口中的方法unlock释放锁
 */

线程的状态

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没有调用start() 方法
Runnable(可运行)线程可以在Java虚拟机中运行的状态,可能正在运行自己的代码,也可能没有,这取决于操作系统处理器
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态,当该线程持有锁时,该线程将变成Runnable状态
Waiting(无线等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
/*
    等待唤醒案例:线程之间的通信
        创建一个顾客线程(消费者):调用wait()方法,放弃cpu的执行权,进入无限等待状态(Waiting)
        创建一个老板线程(生产者):调用notify()方法,唤醒顾客线程
        注意:
            顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个执行
            同步使用锁对象必须保证唯一
            只有锁对象才能调用wait()和notify()方法
        Object类中的方法:
            void wait()
            在其他线程调用此对象的notify()方法或者notifyAll()方法前,导致当前线程等待
            void notify()
            唤醒在此对象监视器上等待的单个线程
            会继续执行wait()方法之后的代码
 */

等待唤醒中的方法:

等待唤醒机制就是用于解决线程通信的问题的,使用到的3个方法的含义如下:

  • wait:线程不在活动,不再参与调度,进入wait set中,因此不会浪费CPU资源,也就不会去竞争锁了,这时的线程即是WAITING。它还要等着别的进程执行一个特别的动作,也就是通知(notify)在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列中(ready queue)中。
  • notify:则选取所通知对象的wait set中的一个线程释放;
  • notifyAll:则释放所通知对象的wait set上的全部线程

注意:

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因此它当初中断的地方是在同步块内,而此刻它已经不再持有锁,所以它需要再次尝试去获取锁(很可能面临其他线程的竞争),成功后才能在当初调用wait方法之后的地方恢复执行。

总结:

  • 如果能获取锁,线程就从WAITING状态变成Runnable状态
  • 否则,从wait set出来,又进入entry set,线程就从WAITING状态又变成Blocked状态

调用wait和notify方法需要注意的细节

  • wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程
  • wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的
  • wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这两个方法

线程池

概念:就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗更多的资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yjrwyrr1-1595949513988)(G:\Snipaste_pic\Snipaste_2020-07-22_14-19-20.png)]

合理使用线程池带来的三个好处:

1.降低系统资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重新利用,可以执行多个任务

2.提高响应速度。当任务下达时,任务可以不需要等到线程创建就能立即执行

3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作的数量,防止因为消耗过多的内存

线程池的原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SP0rx8tu-1595949513990)(G:\Snipaste_pic\Snipaste_2020-07-22_14-16-51.png)]

线程池的使用:

/*
    线程池:JDK1.5之后提供
    java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
    Executors类中的静态方法:
        static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数量的线程池
        参数:int nThreads:创建线程池中包含的线程数量
        返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorServices接口接收(面向接口编程)
    java.util.concurrent.ExecutorService:线程池接口
    用来从线程池中获取线程,调用start方法,执行线程任务
        submit(Runnable task):提交一个Runnable任务用于执行
    关闭/销毁线程池的方法:
        void shutdown()
    线程池的使用步骤:
    1.使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定数量的线程池
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
    3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
    4.调用ExecutorService中的方法shutdow销毁线程池(不建议执行)
 */

1.13lambda的使用前提

  • 使用lambda必须具有接口,且要求接口中有且仅有一个抽象方法

    无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用lambda

  • 使用lambda必须具有上下文推断

    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例

    备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

1.14File类

**概念:**java.io.File类是文件和目录路径的抽象表示,主要用于文件和目录的创建、查找、删除等操作

构造方法

  • public File(String pathname):通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
  • public File(String parent,String child):从父路径名字符串和子路径字符串创建新的File实例
  • public File(File parent,String child):从父抽象路径名和子路径名字符串创建新的File实例

常用方法

  • public String getAbsolutePath():返回此File的绝对路径名字符串
  • public String getPath():将此File转换为路径名字符串
  • public String getName():返回由此File表示的文件或目录的名称
  • public long length():返回由此File表示的文件的长度

判断功能的方法

  • public boolean exists():此File表示的文件或目录是否实际存在
  • public boolean isDirectory():此File表示的是否为目录
  • public boolean isFile():此File表示的是否为文件

创建删除功能

  • public boolean creatNewFile():当且仅当具有该名称的文件尚不存在时,创建一个新的文件
  • public boolean delete():删除由此File表示的文件或目录
  • public boolean mkdir():创建由此File表示的目录
  • public boolean mkdirs():创建由此File表示的目录,包括任何必需但不存在的父目录

1.15字节流

字节输出流(OutputStream)

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void close():关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b,int off,int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
  • public abstract void write(int b):将指定的字节写入此输出流。
/*
    追加写:使用两个参数的构造方法
    FileOutputStream(String name, boolean append):创建一个向具有指定name的文件中写入数据的输出文件流
    FileOutputStream(File file,boolean append):创建一个向指定File对象表示的文件中写入数据的文件输出流
 */

字节输入流(InputStream)

java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法:

  • public void close():关闭输入流并释放与此流相关联的任何系统资源
  • public abstract int read():从输入流读取数据的下一个字节
  • public int read(byte[] b):从输入流中读取一些字节数,并将它们存储到字节数组b中
  • public void close():关闭此输出流并释放与此流相关联的任何系统资源。

1.16字符流

字符输入流

/*
    java.io.Reader:字符输入流最顶层的类,定义了一些共性成员方法,是一个抽象类
    共性成员方法:
        int read()读取单个字符并返回
        int read(char[] buf)一次读取多个字符,将字符读入数组
        void close()关闭该流,并释放与之相关的所有资源
    java.io.FileReader extends InputStreamReader extends Reader
    FileReader:文件字符输入流
    作用:把硬盘文件中的数据以字符的方式读取到内存中
    FileReader(String filename)
    FileReader(File file)

    使用步骤:
        1.创建一个FileReader对象
        2.使用FileReader对象中的方法read读取文件
        3.释放资源
 */

字符输出流

/*
    java.io.Writer:字符输出流,是所有字符输出流的最顶层的父类,是一个抽象类
    共性成员的方法:
        void write(int c)写入单个字符
        void write(char[] cbuf)写入字符数组
        abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分
        void write(String str,int off,int len)写入字符串某一部分
        void flush()刷新该流的缓冲
        void close()关闭此流,但是要先刷新

    java.io,FileWriter extends OutputStreamWriter extends Writer
    FileWriter:文件字符输出流
    作用:把内存中的数据写入到文件中
    构造方法:
        FileWriter(String filename)
        FileWriter(File file)

    使用步骤:
    1.创建一个FileWriter对象,构造方法中绑定要写入数据的目的地
    2.使用FileWriter中的方法write,把数据写入内存缓冲区中(字符转换成字节的过程)
    3.使用FileWriter中的方法flush,把内存缓冲区中的数据刷新到文件中
    4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
 */
/*
    flush方法和close方法的区别
        flush:刷新缓冲区,流对象可以继续使用
        close:先刷新缓冲区,然后通知系统释放资源,流对象不可以再使用了
*/

1.17Properties集合

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;

/*
    java.util.Properties集合 extends Hashtable<k,v> implements Map<k,v>
    Properties集合是一个唯一和IO流相结合的集合
        可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用

    属性列表中每个键及其对应值都是一个字符串
        Properties集合是一个双列集合,key和value默认都是字符串
 */
public class PropertiesDemo1 {
    public static void main(String[] args) throws IOException {
//        show1();
//        show2();
        show3();
    }
    /*
        可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
        void load(InputStream inStream)
        void load(Reader reader)
        参数:
            InputStream inStream:字节输入流,不能读取含有中文的键值对
            Reader reader:字符输入流,能读取含有中文的键值对
        使用步骤:
            1.创建Properties集合对象
            2.使用Properties集合对象中的方法load读取保存键值对的文件
            3.遍历Properties集合
        注意:
            1.存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其他符号)
            2.存储键值对的文件中,可以使用#进行注释,注释的键值对不会再被读取
            3.存储键值对的文件中,键与值默认都是字符串,不用再加引号
     */
    private static void show3() throws IOException {
        Properties prop=new Properties();
        prop.load(new FileReader("src\\com\\asus\\demo25\\c.txt"));
        Set<String> set = prop.stringPropertyNames();
        for (String s : set) {
            System.out.println(s+"="+prop.getProperty(s));
        }
    }

    /*
        可以使用Properties集合中的方法store,把集合中的临时数据,持续化写入到硬盘中存储
        void store(OutputStream out,String comments)
        void store(Writer writer,String comments)
        参数:
            OutputStream out:字节输出流,不能写入中文
            Writer writer:字符输出流,可以写中文
            String comments:注释,用来解释说明保存的文件是做什么用的
                不能使用中文,会产生乱码,默认是Unicode编码
                一般使用”“(空字符串)
       使用步骤:
            1.创建Properties集合对象,添加数据
            2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
            3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入硬盘中存储
            4.释放资源
     */
    private static void show2() throws IOException {
        Properties prop=new Properties();
        prop.setProperty("张三","29");
        prop.setProperty("李四","19");
        prop.setProperty("王五","39");
        FileWriter fw=new FileWriter("src\\com\\asus\\demo25\\c.txt");
        prop.store(fw,"save data");
        fw.close();
    }

    /*
        使用Properties集合存储数据,遍历取出Properties集合中的数据
        Properties集合是一个双列集合,key和value都默认是字符串
        Properties集合有一些操作字符串的特有方法
            Object setProperties(String key,String value)调用Hashtable的方法put
            String getProperties(String key)通过key找到value值,此方法相当于Map集合中的get(key)方法
            set<String> stringPropertyNames()返回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
     */
    private static void show1() {
        //创建一个Properties集合对象
        Properties prop=new Properties();
        prop.setProperty("张三","29");
        prop.setProperty("李四","19");
        prop.setProperty("王五","39");
        //使用stringPropertyNames把Properties集合中的键取出来,存储到一个Set集合中
        Set<String> set=prop.stringPropertyNames();
        //遍历Set集合
        for (String s : set) {
            System.out.println(s+"="+prop.getProperty(s));
        }
    }
}

1.18缓冲流

字节缓冲输入流

package com.asus.demo27;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

/*
    java.io.BufferedInputStream extends InputStream  字节缓冲输入流
    继承自父类的成员方法:
        int read()从输入流中读取数据的下一个字节
        int read(bytr[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
        void close()关闭此输入流并释放与该流关联的所有系统资源
    构造方法:
        创建一个BufferedInputStream并保存其参数,即输入流in
        BufferedInputStream(InputStream in)
        创建具有指定缓冲区大小的BufferedInputStream并保存其参数
        BufferedInputStream(InputStream in,int size)
        参数:
            InputStream in:字节输入流
            我们可以传递FileInputStream对象,缓冲流会给该对象增加一个缓冲区,提高读取效率
            int size:指定缓冲流内部缓冲区大小,不指定默认
        使用步骤:
            1.创建FileInputStream对象,构造方法中要绑定要读取的数据源
            2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
            3.使用BufferedInputStream对象中的方法read,读取文件
            4.释放资源
 */
public class BufferedInputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis=new FileInputStream("src\\com\\asus\\demo23\\a.txt");
        BufferedInputStream bis=new BufferedInputStream(fis);
        byte[] bytes=new byte[1024];
        int len=0;
        while((len=bis.read(bytes))!=-1)
        {
            System.out.println(new String(bytes,0,len));
        }
        bis.close();
    }
}

字节缓冲输出流

package com.asus.demo27;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;

/*
    java.io.BufferedOutputStream extends OutputStream
    BufferedOutputStream:字符缓冲输出流
    继承自父类的共性成员方法:
        public void close()
        public void flush()
        public void write(byte[] b)
        public void write(byte[] b,int offset,int length)
        public abstract void write(int b)
    构造方法:
        BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流
        BufferedOutputStream(OutputStream out,int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入底层输出流
    参数:
        OutputStream out:字节输出流
            我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
        int size:指定缓冲流内部缓冲区的大小,如果不指定就默认
    使用步骤:
        1.创建一个FileOutputStream对象,构造方法中绑定要输出的目的地
        2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象的效率
        3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        4.使用BufferedOutputStraem对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
        5.释放资源(会先调用flush方法刷新数据,第四步可以省略)
 */
public class BufferedOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("src\\com\\asus\\demo25\\c.txt");
        BufferedOutputStream bos=new BufferedOutputStream(fos);
        byte[] bytes={'j','a','v','a'};
        bos.write(bytes);
        bos.close();
    }
}

字符缓冲输出流

package com.asus.demo27;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/*
    BufferedWriter(Writer out)创建一个使用默认大小输出缓冲区的缓冲字符输出流
    BufferedWriter(Writer out,int size)创建一个使用给定大小输出缓冲区的新缓冲字符输出流
    参数:
        Writer out:字符输出流
        int size:指定缓冲区大小
    特有成员方法:
    void newLine()写入一个行分隔符。会根据不同的操作系统获取不同的行分隔符
    使用步骤:
        1.创建字符缓冲输出流对象,构造方法中传递字符输出流
        2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区
        3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
        4.释放资源
 */
public class BufferedWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fw=new FileWriter("G:\\CodeFile\\demoPractice\\src\\com\\asus\\demo27\\a.txt",true);
        BufferedWriter bw=new BufferedWriter(fw);
        for (int i = 0; i < 10; i++) {
            bw.write("hello world!");
//            bw.write("\r\n");
            bw.newLine();
        }
        bw.flush();
        bw.close();
    }
}

字符缓冲输入流

package com.asus.demo27;
/*
    java.io.BufferedReader extends Reader
    公共成员方法:
        int read()读取单个字符并返回
        int read(char[] cbuf)一次读取多个字符,将字符读入数组
        void close()关闭该流并释放与之相关联的资源
    特有成员方法:
        String readLine()读取一个文本行。读取一行数据
            行的终止符:换行符('\n')、回车('\r')或者回车后面直接跟着换行(\r\n)
            返回值:包含该行内容的字符串,不包含任何终止符,如果已经到达流末尾,则返回null
    使用步骤:
        1.创建字符缓冲输入流对象,构造方法中传递字符输入流
        2.使用字符缓冲输入流对象中的方法read/readLine读取文本
        3.释放资源
 */
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        FileReader fr=new FileReader("G:\\CodeFile\\demoPractice\\src\\com\\asus\\demo27\\b.txt");
        BufferedReader br=new BufferedReader(fr);
        /*char[] chars=new char[1024];
        int len=0;
        while((len=br.read(chars))!=-1)
        {
            System.out.println(new String(chars,0,len));
        }*/
        String line;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
        br.close();
    }
}

1.19转换流

写转换流OutputStreamWriter

package com.asus.demo28;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

/*
    java.io.OutputStreamWriter extends Writer
    OutputStreamWriter:是字符流通向字节流的桥梁,可使用指定的charset将要写入流中的字符编码成字节
    继承自父类的共性方法:
        void write(int c)写入单个字符
        void write(char[] cbuf)写入字符数组
        abstract void write(char[] cbuf,int offset,int length)
        void write(String str)
        void write(String str,int offset,int length)
        void flush()
        void close()
    构造方法:
        OutputStreamWriter(OutputStream out)创建使用默认的字符编码的OutputStreamWriter
        OutputStreamWriter(OutputStream out,String charsetName)创建使用指定字符集的OutputStreamWriter
    参数:
        Outputstream out:字节输出流,可以用来写转换之后的字节到文件中
        String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/gbk,默认是utf-8
    使用步骤:
        1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        2.使用OutputStreamWriter对象中的write,把字符转换为字节存储缓冲区中(编码)
        3.使用OutputStreamWriter对象中的flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        4.释放资源
 */
public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
//        OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("G:\\CodeFile\\demoPractice\\src\\com\\asus\\demo28\\utf-8.txt"),"utf-8");
        OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("G:\\CodeFile\\demoPractice\\src\\com\\asus\\demo28\\gbk.txt"),"gbk");
        osw.write("这是一个UTF-8文件!");
        osw.flush();
        osw.close();
    }
}

读转换流InputStreamReader

package com.asus.demo28;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/*
    java.io.InputStreamReader
    InputStreamReader:是字节流通向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符

    继承自父类的共性成员方法:
        int read()读取单个字符并返回
        int raed(char[] cbuf)一次读取多个字符,将字符读入数组
        void close()
    构造方法:
         InputStreamReader(InputStream in)创建一个使用默认字符集的InputStreamReader
         InputStreamReader(InputStream in,String charsetName)创建使用指定字符集的InputStreamReader
     参数:
        InputStream in:字节输入流,用来读取文件中保存的字节
        String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/gbk,默认是utf-8
    使用步骤:
        1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        2.使用InputStreamReader对象中的read方法读取文件
        3.释放资源
 */
public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr=new InputStreamReader(new FileInputStream("G:\\CodeFile\\demoPractice\\src\\com\\asus\\demo28\\gbk.txt"),"gbk");
        int len=0;
        while((len=isr.read())!=-1)
        {
            System.out.print((char)len);
        }
        isr.close();
    }
}

1.20序列化与反序列化

原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QW3Z6hW7-1595949513992)(G:\Snipaste_pic\Snipaste_2020-07-26_00-24-44.png)]

序列化

package com.asus.demo29;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/*
    java.io.ObjectOutputStream extends OutputStream对象的序列化流
    构造方法:
        ObjectOutputStream(OutputStream out)创建写入指定OutputStream的ObjectOutputStream
    特有的成员方法:
        void writeObject(Object obj)将指定的对象写入ObjectOutputStream
    使用步骤:
        1.创建ObjectOutputStream对象,构造方法中传递字节输出流
        2.使用ObjectOutputStream对象中的方法writeObject,把对象写入文件中
        3.释放资源
 */
public class ObjectOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("src\\com\\asus\\demo29\\person.txt"));
        oos.writeObject(new Person("张三",20));
        oos.close();
    }
}

反序列化

package com.asus.demo29;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/*
    java.io.ObjectInputStream extends InputStream  对象的反序列化流
    作用:把文件中保存的对象以流的方式读取出来使用

    构造方法:
        ObjectInputStream(InputStream in)创建从指定InputStream读取的ObjectInputStream
    特有的成员方法:
        Object readObject()从ObjectInputStream读取对象
    使用步骤:
        1.创建ObjectInputStream对象,构造方法中传递字节输入流
        2.使用ObjectInputStream对象中的方法readObject读取保存的文件
        3.释放资源
        4.使用读取出来的对象(打印……)
    readObject()方法声明抛出了ClassNotFoundException(class文件找不到异常)
    当不存在对象的class文件时抛出此异常
    反序列化的前提:
        1.类必须实现Serializable
        2.必须存在类对应的class文件
 */
public class ObjectInputStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("src\\com\\asus\\demo29\\person.txt"));
        Object object = ois.readObject();
        System.out.println(object);
        ois.close();
    }
}

InvalidClassException异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIfZ9NIo-1595949513994)(G:\Snipaste_pic\Snipaste_2020-07-26_09-15-26.png)]

1.21打印流

打印输出流

package com.asus.demo30;

import java.io.IOException;
import java.io.PrintStream;

/*
    java.io.PrintStream:打印流
    特点:
        1.只负责数据的输出,不负责数据的读取
        2.与其他输出流不同,PrintStream永远不会抛出IOException
        3.特有成员方法print()、println()
    构造方法:
        PrintStream(File file):输出目的地是一个文件
        PrintStream(OutputStream out):输出目的地是一个字节输出流
        PrintStream(String fileName):输出目的地是一个文件路径
    PrintStream extends OutputStream
    继承自父类的成员方法:
        public void close()
        public void flush()
        public void write(byte[] b)
        public void write(byte[] b,int off,int len)
        public abstract void write(int b)
    注意事项:
        如果使用继承的write方法写数据,那么查看数据的时候会查询编码表  97->a
        如果使用自己特有的方法print()、println()写数据,写的数据原样输出 97->97
 */
public class PrintStreamDemo1 {
    public static void main(String[] args) throws IOException {
        PrintStream ps=new PrintStream("src\\com\\asus\\demo30\\print.txt");
        ps.println("hello world!");
        ps.write("This is a test!".getBytes());
        ps.close();
    }
}

1.22TCP通信程序

在Java中,提供了两个类用于实现TCP通信程序:

  • 客户端:java.net.Socket类表示。创建Socket对象,向服务器发出连接请求,服务端响应请求,两者建立连接开始通信。
  • 服务器端:java.net.ServerSocket类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HnIFNyEM-1595949513995)(G:\Snipaste_pic\Snipaste_2020-07-26_13-11-18.png)]

客户端

package com.asus.demo31;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*
    TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回送的数据
    表示客户端的类:
        java.io.Socket:此类实现客户端套接字。套接字是两台机器通信的端点
    构造方法:
        Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号
    参数:
        String host:服务器主机的名称/服务器的IP地址
        int port:服务器的端口号
    成员方法:
        OutputStream getOutputStream()返回此套接字的输出流
        InputStream getInputStream()返回此套接字的输入流
        void close()关闭此套接字
    实现步骤:
        1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        3.使用网络字节输出流Outputstream对象中的方法write,给服务器发送数据
        4.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        6.释放资源(Socket)
    注意:
        1.客户端和服务器进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
        2.当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过三次握手建立连接通路
            这时如果服务器没有启动,那么就会抛出异常
            如果服务器已经启动,那么就可以进行交互了

 */
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[] bytes=new byte[1024];
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        os.close();
        is.close();

    }
}

服务器端

package com.asus.demo31;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
    TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
    表示服务器的类:java.net.ServerSocket
    构造方法:
        ServerSocket(int port)创建绑定到特定端口号的服务器套接字
        服务器端必须明确一件事:必须知道是哪一个客户端请求的服务器
        所以可以使用accept()方法获取请求的客户端对象Socket
    成员方法:
        Socket accept()侦听并接受到此套接字的连接
    服务器实现的步骤:
        1.创建服务器ServerSocket对象和系统要指定的端口号
        2.使用ServerSocket对象中的accept()方法获取到请求的客户端对象Socket
        3.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        4.使用网络字节输入流InputStream对象中的方法read()读取客户端发来的数据
        5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        6.使用网络字节输出流OutputStream对象中的write(),给客户端回写数据
        7.释放资源(Socket,ServerSocket)
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server=new ServerSocket(8888);
        Socket socket = server.accept();
        InputStream is = socket.getInputStream();
        byte[] bytes=new byte[1024];
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        OutputStream os = socket.getOutputStream();
        os.write("服务器已经收到信息,谢谢使用!".getBytes());
        socket.close();
        server.close();
    }
}

案例:本地文件上传到服务器

//客户端程序
package com.asus.demo32;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket("127.0.0.1",9999);
        FileInputStream fis=new FileInputStream("src\\com\\asus\\demo32\\Client\\a.txt");
        OutputStream outputStream = socket.getOutputStream();
        byte[] bytes=new byte[1024];
        int len=0;
        while((len=fis.read(bytes))!=-1)
        {
            outputStream.write(bytes,0,len);
        }
        socket.shutdownOutput();
        InputStream inputStream = socket.getInputStream();
        while((len=inputStream.read(bytes))!=-1)
        {
            System.out.println(new String(bytes,0,len));
        }
        fis.close();
        socket.close();
    }
}
//服务器端程序
package com.asus.demo32;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

public class Server {
    public static void main(String[] args) throws IOException {

        ServerSocket server=new ServerSocket(9999);
        while(true)
        {
            Socket socket = server.accept();
            new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            try
                            {
                                File file=new File("src\\com\\asus\\demo32\\server");
                                if(!file.exists())
                                {
                                    file.mkdirs();
                                }
                                String fileName="IT"+System.currentTimeMillis()+new Random().nextInt(99999)+".txt";
                                FileOutputStream fos=new FileOutputStream(file+"\\"+fileName);

                                InputStream inputStream = socket.getInputStream();
                                byte[] bytes=new byte[1024];
                                int len=0;
                                while((len=inputStream.read(bytes))!=-1)
                                {
                                    fos.write(bytes,0,len);
                                }
                                socket.shutdownInput();
                                OutputStream outputStream = socket.getOutputStream();
                                outputStream.write("上传完成!".getBytes());
                                fos.close();
                                socket.close();
                            }catch(IOException e)
                            {
                                e.printStackTrace();
                            }
                        }
                    }
            ).start();
        }
    }
}

BS案例

package com.asus.demo33;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

//创建BS版本的TCP服务器
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建一个服务器SerevrSocket,和系统要指定的端口号
        ServerSocket server=new ServerSocket(8080);

        /*
            浏览器解析服务器回写html页面,页面中如果有图片,那么浏览器就会单独开启一个线程,读取服务器的图片
            我们就得让服务器一直处于监听状态,客户端请求一次,服务器就回写一次
         */
        while(true) {
            //使用accept()获取到请求的客户端对象(浏览器)
            Socket socket = server.accept();
            new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            try {
                                //使用Socket对象中的方法getInputStream(),获取到网络字节输入流InputStream对象
                                InputStream is = socket.getInputStream();
                                //使用网络字节输入流InputStream对象中方法read()读取客户端的请求信息
                                /* byte[] bytes=new byte[1024];
                                int len=0;
                                while((len=is.read(bytes))!=-1)
                                {
                                    System.out.println(new String(bytes,0,len));
                                }*/
                                //把is网络字节输入流对象转换成字符缓冲输入流
                                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                                //把客户端请求信息的第一行读取出来 GET /G:/CodeFile/demoPractice/web/test.html HTTP/1.1
                                String line = br.readLine();
                                //字符串切割,截取中间部分
                                String[] arr = line.split(" ");
                                //把路径前面的/去掉
                                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[] bytes = new byte[1024];
                                while ((len = fis.read(bytes)) != -1) {
                                    os.write(bytes, 0, len);
                                }
                                //释放资源
                                fis.close();
                                socket.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
            ).start();
        }
        //server.close();
    }
}

1.23函数式接口

**概念:**有且只有一个抽象方法的接口

格式:

修饰符 interface 接口名称
{
    public abstract 返回值类型 方法名称(可选参数信息);
    //其他非抽象方法内容
}
//注意:接口中的抽象方法public abstract是可以省略的

1.24函数式编程

Lambda的延迟执行

性能浪费的日志案例

注:日志可以帮助我们快速定位问题,记录程序运行过程中的情况,以便项目的监控和优化

一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出

public class LoggerDemo
{
    private static void log(int level,String msg)
    {
        if(level==1)
        {
            System.out.println(msg);
        }
    }
    public static void main(String[] args)
    {
        String msgA="Hello";
        String msgB="World";
        String msgC="Java";
        log(1,msgA+msgB+msgC);
    }
}

这段代码存在问题,无论级别是否满足要求,作为log方法的第二个参数,三个字符串一定会被自动拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费

优化

package com.asus.demo35;

@FunctionalInterface
public interface MessageBuilder {
    //定义一个拼接消息的抽象方法,返回被拼接的消息
    public abstract String buildMessage();
}


package com.asus.demo35;
/*
    使用Lambda优化日志案例
    Lambda特点:延迟加载
    Lambda的使用前提,必须存在函数式接口
 */
public class LambdaDemo {
    //定义一个显示日志的方法
    public static void showLog(int level,MessageBuilder mb)
    {
        if(level==1)
        {
            System.out.println(mb.buildMessage());;
        }
    }
    public static void main(String[] args) {
        String msg1="hello";
        String msg2="world";
        String msg3="java";
        showLog(2,()-> msg1+msg2+msg3);
        /*
            使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog()方法中
            只要满足条件,日志的等级是1级
                才会调用接口MessageBuilder中的方法buildMessage()
                才会进行字符串的拼接
            如果条件不满足,日志的等级不是1级
                那么MessageBuilder接口中的方法buildMessage()也不会执行
                所以拼接字符串的代码也不会执行
            所以不会存在性能浪费问题
         */
    }
}

使用Lambda作为参数和返回值

抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

例如java.lang.Runnable接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和Thread类的构造方法参数为Runnable没有本质区别。

package com.asus.demo36;

public class RunnableDemo {
    //定义一个方法startThread(),方法的参数使用函数式接口Runnable
    public static void startThread(Runnable run)
    {
        //开启多线程
        new Thread(run).start();
    }

    public static void main(String[] args) {
        //调用startThread()方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"->"+"线程启动了");
            }
        });
        //调用startThread()方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
        startThread(()-> System.out.println(Thread.currentThread().getName()+"->"+"线程启动了"));
    }
}

类似的,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法获取一个java.util.Compator接口类型的对象作为排序器时,就可以调该方法获取。

package com.asus.demo36;

import java.util.Arrays;
import java.util.Comparator;

public class ComparatorDemo {
    //定义一个方法,方法的返回值类型使用函数式接口Comparator
    public static Comparator<String> getComparator()
    {
        //方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
       /* return new Comparator<String>()
        {
            @Override
            public int compare(String o1, String o2) {
                //按照字符串的降序排序
                return o1.length()-o2.length();
            }
        };*/
        //方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式
        return (o1,o2)->o1.length()-o2.length();
    }

    public static void main(String[] args) {
        //创建一个字符串数组
        String[] arr={"aaa","b","fthrd","gg"};
        Arrays.sort(arr,getComparator());
        System.out.println(Arrays.toString(arr));
    }
}

1.25常用函数式接口

  • Supplier接口

    java.util.function.Supplier

    接口仅包含一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

    package com.asus.demo37;
    
    import java.util.function.Supplier;
    /*
        Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
     */
    public class SupplierDemo {
        //定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
        public static String getString(Supplier<String> sup)
        {
            return sup.get();
        }
    
        public static void main(String[] args) {
            //调用getString()方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
            String s = getString(() -> "hello world!");
            System.out.println(s);
        }
    }
    
    
  • Consumer接口

    java.util.function.Consumer接口则正好与Supplier接口相反,它不是产生一个数据,而是消费一个数据,其数据类型由泛型决定。

    抽象方法:accept

    Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用如:

    package com.asus.demo37;
    
    import java.util.function.Consumer;
    /*
        Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
     */
    public class ConsumerDemo {
        /*
            定义一个方法
            方法的参数传递一个字符串姓名
            方法的参数传递Consumer接口,泛型使用String
            可以使用Consumer接口消费字符串的姓名
         */
        public static void consumerString(String name,Consumer<String> consumer)
        {
            consumer.accept(name);
        }
        public static void main(String[] args) {
            //调用consumerString()方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
            consumerString("Alice",(String name)->
            {
                //对传递的字符串进行消费
                System.out.println(name);
                //将字符串进行反转
                String reName=new StringBuffer(name).reverse().toString();
                System.out.println(reName);
            });
        }
    }
    
    

    默认方法:andThen

    如果一个方法的参数和返回值全部都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen().JDK源码如下:

    default Consumer<T> andThen(Consumer<? super T> after)
    {
        Objects.requireNonNull(after);
        return (T t) -> {accept(t);after.accept(t);};
    }
    /*
    	java.util.Objects的requireNonNull静态方法将会在参数为null时主动抛出NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
    */
    
    package com.asus.demo37;
    
    import java.util.function.Consumer;
    /*
        Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
     */
    public class ConsumerDemo {
        /*
            定义一个方法
            方法的参数传递一个字符串姓名
            方法的参数传递Consumer接口,泛型使用String
            可以使用Consumer接口消费字符串的姓名
         */
        public static void consumerString(String name,Consumer<String> consumer)
        {
            consumer.accept(name);
        }
        public static void main(String[] args) {
            //调用consumerString()方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
            consumerString("Alice",(String name)->
            {
                //对传递的字符串进行消费
                System.out.println(name);
                //将字符串进行反转
                String reName=new StringBuffer(name).reverse().toString();
                System.out.println(reName);
            });
        }
    }
    /*
    HELLO,WORLD
    hello,world
    -------------------
    hello,world
    HELLO,WORLD
    */
    

    例题

    package com.asus.demo37;
    
    import java.util.function.Consumer;
    
    public class TestDemo2 {
        public static void printOut(String[] arr, Consumer<String> con1,Consumer<String> con2)
        {
            for (String message : arr) {
                con1.andThen(con2).accept(message);
            }
        }
    
        public static void main(String[] args) {
            String[] array={"Alice,20","Helen,25","Jackson,29"};
            printOut(array,
                    (message)->{
                        String name = message.split(",")[0];
                        System.out.print("name:"+name);
                    },
                    (message)->{
                        String age = message.split(",")[1];
                        System.out.println(" age:"+age);
                    });
        }
    }
    /*
    name:Alice age:20
    name:Helen age:25
    name:Jackson age:29
    */
    
  • predicate接口

    有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate接口。

    抽象方法:test

    predicate接口中包含一个抽象方法:boolean test(T t).用于条件判断的场景:

    package com.asus.demo38;
    
    import java.util.function.Predicate;
    
    public class PredicateDemo {
        /*
            定义一个方法
            参数传递一个String类型的字符串
            传递一个Predicate接口,泛型使用String
            使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
         */
        public static boolean checkString(String str, Predicate<String> pre)
        {
            return pre.test(str);
        }
    
        public static void main(String[] args) {
            //定义一个字符串
            String s="rthrth";
            //调用checkString()对字符串进行校验,参数传递字符串和Lambda表达式
            //对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
            boolean b = checkString(s, (str) -> (str.length() > 5));
            System.out.println(b);
        }
    }
    

    默认方法:and

    将两个predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and。JDK源码如下:

    default Predicate<T> and(Predicate<? super T>other)
    {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    

    默认方法:or

    与and的“与”类似,默认方法or实现逻辑关系中的“或”。JDK源码如下:

    default Predicate<T> or(Predicate<? super T> other)
    {
        Object.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    

    默认方法:negate

    “与”、“或”,negate实现“非”。默认方法negate的JDK源码为:

    default Predicate<T> negate()
    {
        return (t) -> !test(t);
    }
    

    案例:集合信息筛选

    package com.asus.demo38;
    
    import java.util.ArrayList;
    import java.util.function.Predicate;
    
    public class PredicateDemo5 {
        public static boolean checkString(String str, Predicate<String> pre1,Predicate<String> pre2)
        {
                return pre1.and(pre2).test(str);
        }
    
        public static void main(String[] args) {
            String arr[]={"迪丽热巴,女","古力娜扎,女","林俊杰,男","卫宫士郎,男"};
            ArrayList<String> arrayList=new ArrayList<>();
            for (String s : arr)
            {
                boolean b = checkString(s,
                        (str) -> {
                            String name = str.split(",")[0];
                            return name.length() == 4;
                        }, (str) -> {
                            String sex = str.split(",")[1];
                            return sex.equals("女");
                        });
                if(b)
                {
                    arrayList.add(s);
                }
            }
            for (String string : arrayList) {
                System.out.println(string);
            }
        }
    }
    
  • Function接口

    java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,前者成为前置条件,后者称为后置条件。

    抽象方法:apply

    Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。

    package com.asus.demo39;
    
    import java.util.function.Function;
    
    public class FunctionApplyDemo {
        public static int change(String s,Function<String,Integer> function)
        {
            Integer in = function.apply(s);
            return in;
        }
    
        public static void main(String[] args) {
            String s="5438";
            int i = change(s, (str) -> Integer.parseInt(str));
            System.out.println(i);
        }
    }
    

    默认方法:andThen

    Function接口中有一个默认的andThen()方法,用来进行组合操作。JDK源代码如下:

    default <V> Function<T,V> andThen(Function<? super R, ? extends V> after)
    {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    
    package com.asus.demo39;
    
    import java.util.function.Function;
    
    public class Function_andThenDemo {
        public static String change(String str, Function<String,Integer> fun1,Function<Integer,String> fun2)
        {
            return fun1.andThen(fun2).apply(str);
        }
    
        public static void main(String[] args) {
            String s="123";
            String string = change(s,
                    (str) -> {
                        int i = Integer.parseInt(str);
                        return i + 10;
                    },
                    (str) -> {
                        return String.valueOf(str);
                    });
            System.out.println(string);
        }
    }
    

1.26Stream流

案例

package com.asus.demo40;

import java.util.ArrayList;
import java.util.List;
/*
    使用Stream流的方式,遍历集合,对集合中的数据进行过滤,Stream流是JDK1.8之后出现的
 */
public class StreamDemo1 {
    public static void main(String[] args) {
        //创建一个List集合,存储姓名
        List<String> list=new ArrayList<>();
        list.add("胡歌");
        list.add("赵丽颖");
        list.add("张无忌");
        list.add("周芷若");
        list.add("张三丰");
        list.add("张三");
        //对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
        //再对新集合进行过滤,只要姓名长度为3的人,存储到一个新集合中,遍历该集合
        list.stream()
                .filter(name->name.startsWith("张"))
                .filter(name->name.length()==3)
                .forEach(name-> System.out.println(name));
    }
}

获取流

java.util.stream.Stream是Java8新加入的最常用的流接口。(这并不是一个函数式接口)

获取一个流非常简单,有以下几种常用的方式:

  • 所有的Collection集合都可以通过stream默认方法获取流
  • Stream接口的静态方法of可以获取数组对应的流

根据Collection获取流

package com.asus.demo40;

import java.util.*;
import java.util.stream.Stream;

/*
    java.util.stream.Stream<T>是Java 8新加入的最常用的流接口
    获取流接口的方法:
        所有Collection集合都可以通过stream默认方法获取流;
            default Stream<E> stream()
        Stream接口的静态方法of可以获取数组对应的流。
            static <T> Stream<T> of(T …… values)
            参数是一个可变参数,那么我们可以传递一个数组
 */
public class GetStreamDemo {
    public static void main(String[] args) {
        //把集合转换成为Stream流
        List<String> list=new ArrayList<>();
        Stream<String> stream1=list.stream();

        Set<String> set=new HashSet<>();
        Stream<String> stream2 = set.stream();

        Map<String,String> map=new HashMap<>();
        //获取键,存储到一个Set集合中去
        Set<String> keyset = map.keySet();
        Stream<String> stream3 = keyset.stream();

        //获取值,存储到一个Collection集合中去
        Collection<String> values=map.values();
        Stream<String> stream4 = values.stream();

        //获取键值对(键与值的映射关系 entrySet)
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream5 = 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);
        String[] arr2={"a","bb","ccc"};
        Stream<String> stream8 = Stream.of(arr2);

    }
}

常用方法

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终止方法外,其余方法均为延迟方法)
  • 终止方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。例如:count、forEach方法

逐一处理:forEach

package com.asus.demo40;

import java.util.stream.Stream;

/*
    void forEach(Consumer<? super T> action);该方法接受一个Consumer接口函数,会将每一个流元素交给该函数进行处理
    Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据

    简言之:
        forEach()方法,用来遍历流中的数据
        是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
 */
public class Stream_forEach {
    public static void main(String[] args) {
        //获取一个stream流
        Stream<String> stream=Stream.of("Helen","Alice","Saber","Tom","Jackson");
        stream.forEach(name-> System.out.println(name));
    }
}

过滤:filter

可以通过filter方法将一个流转换成另一个子集流。

package com.asus.demo40;

import java.util.stream.Stream;

/*
    Stream流中常用的方法filter:用于对Stream流中的数据进行过滤
    Stream<T> filter(Predicate<? super T> predicate);
    filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
 */
public class Stream_filter {
    public static void main(String[] args) {
        //创建一个Stream流
        Stream<String> stream1 = Stream.of("张三丰", "张无忌", "赵敏", "周芷若", "张翠山");
        Stream<String> stream2 = stream1.filter(name -> name.startsWith("张"));
        stream2.forEach(name-> System.out.println(name));
        /*
            stream流属于管道流,只能被消费一次
            第一个stream流调用完毕方法,数据就会流转到下一个Stream上
            而这时第一个Stream流已经使用完毕,就会关闭了
            所以第一个Stream流就不能再调用方法了
         */
    }
}

映射:map

如果需要将流中的元素映射到另一个流中,可以使用map方法。

<R> Stream<R> map(Function<? super T,? extends R> mapper);

案例

package com.asus.demo40;

import java.util.stream.Stream;

/*
    如果需要将流中的元素映射到另一个流中,可以使用map方法。
    <R> Stream<R> map(Function<? super T,? extends R> mapper);
    该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的流
    Function中的抽象方法:
        R apply(T t);
 */
public class Stream_map {
    public static void main(String[] args) {
        //获取一个String类型的stream流
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //使用map方法,把字符串类型的整数转换成(映射)为Integer类型的整数
        Stream<Integer> stream1 = stream.map(s -> Integer.parseInt(s));
        //遍历
        stream1.forEach(i-> System.out.println(i));
    }
}

统计个数:count

正如旧集合Collection当中的size方法一样,流提供的count方法来数一数其中的元素个数,是一个终结方法:

long count();

取用前几个:limit

limit方法可以对流进行截取,只取用前n个。

是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法。

stream<T> limit(long maxSize);

跳过前几个:skip

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

Stream<T> skip(long n);

组合:concat

如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat:

static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T>b)

1.27方法引用

通过对象名引用成员方法

package com.asus.demo41;

public interface Printable {
    public abstract void print(String s);
}
package com.asus.demo41;

public class Method {
    public void printUpperCase(String str)
    {
        System.out.println(str.toUpperCase());
    }
}
package com.asus.demo41;

public class MethodDemo {
    public static void printString(Printable p)
    {
        p.print("hello,world");
    }
    public static void main(String[] args) {
        printString(s-> System.out.println(s.toUpperCase()));


        printString(s->
            new Method().printUpperCase(s)
        );

        //方法引用优化Lambda表达式
        Method obj = new Method();
        printString( obj::printUpperCase);
    }
}

通过类名称引用静态方法

package com.asus.demo42;
@FunctionalInterface
public interface Calsable {
    public abstract int calsAbs(int n);
}
package com.asus.demo42;

public class Calculate {
    public static int method(int n,Calsable cal)
    {
        return cal.calsAbs(n);
    }

    public static void main(String[] args) {
        int i = method(-10, number -> Math.abs(number));
        System.out.println(i);

        int j = method(-100, Math::abs);
        System.out.println(j);
    }
}

通过super引用成员方法

package com.asus.demo43;
@FunctionalInterface
public interface Greetable {
    void greet();
}
package com.asus.demo43;

public class Father {
    public void sayHello()
    {
        System.out.println("Hello,I am father!");
    }
}
package com.asus.demo43;

public class Son extends Father{
    @Override
    public void sayHello() {
        System.out.println("Hello,I am son!");
    }
    public void method(Greetable g)
    {
        g.greet();
    }
    public void show()
    {
/*        method(()->{
            Father father = new Father();
            father.sayHello();
        });*/

        //因为有子父类关系,所以存在的一个关键字super代表父类,我们可以直接使用super调用父类的成员方法
        //method(()->{super.sayHello();});

        /*
            使用方法引用优化Lambda表达式
            使用super引用类的成员方法
            super是已经存在的
            父类的成员方法sayHello()也是存在的
            所以我们可以直接使用super引用父类的成员方法
         */
        method(super::sayHello);
    }

    public static void main(String[] args) {
        Son son = new Son();
        son.show();
    }
}

通过 this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。

类的构造器引用

package com.asus.demo44;
@FunctionalInterface
public interface PersonBuilder {
    //定义一个方法,根据传递的姓名,创建Person对象返回
    Person buildPerson(String name);
}
package com.asus.demo44;

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
package com.asus.demo44;
/*
    类的构造器(构造方法)引用
 */
public class Test {
    //定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
    public static void printName(String name,PersonBuilder pb)
    {
        Person person = pb.buildPerson(name);
        System.out.println(person);
    }

    public static void main(String[] args) {
        //调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda表达式
        printName("赵丽颖",name->new Person(name));
        /*
            使用方法引用优化Lambda表达式
            构造方法new Person(name)已知
            创建对象已知 new
            就可以使用Person引用new创建对象
         */
        printName("杨幂",Person::new);
    }
}

数组的构造器引用

package com.asus.demo45;
/*
    一个创建数组的函数式接口
 */
@FunctionalInterface
public interface ArrayBuilder {
    //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
    int[] buildArray(int length);
}
package com.asus.demo45;
/*
    数组的构造器引用
 */
public class Test {
    /*
        定义一个方法,方法的参数传递创建数组的长度和ArrayBuilder接口
        方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
     */
    public static int[] createArray(int length,ArrayBuilder ab)
    {
        return ab.buildArray(length);
    }

    public static void main(String[] args) {
        //调用createArray()方法,传递数组的长度和Lambda表达式
        int[] array = createArray(3, len -> new int[len]);
        System.out.println(array.length);
        /*
            使用方法引用优化Lambda表达式
            已知创建的就是int[]数组
            数组的长度也是已知的
            就可以使用方法引用
            int[]引用new,根据参数传递的长度来创建数组
         */
        int[] array1 = createArray(6, int[]::new);
        System.out.println(array1.length);
    }
}

ethod(()->{super.sayHello();});

    /*
        使用方法引用优化Lambda表达式
        使用super引用类的成员方法
        super是已经存在的
        父类的成员方法sayHello()也是存在的
        所以我们可以直接使用super引用父类的成员方法
     */
    method(super::sayHello);
}

public static void main(String[] args) {
    Son son = new Son();
    son.show();
}

}


**通过 this引用成员方法**

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。

**类的构造器引用**

```java
package com.asus.demo44;
@FunctionalInterface
public interface PersonBuilder {
    //定义一个方法,根据传递的姓名,创建Person对象返回
    Person buildPerson(String name);
}
package com.asus.demo44;

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
package com.asus.demo44;
/*
    类的构造器(构造方法)引用
 */
public class Test {
    //定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
    public static void printName(String name,PersonBuilder pb)
    {
        Person person = pb.buildPerson(name);
        System.out.println(person);
    }

    public static void main(String[] args) {
        //调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda表达式
        printName("赵丽颖",name->new Person(name));
        /*
            使用方法引用优化Lambda表达式
            构造方法new Person(name)已知
            创建对象已知 new
            就可以使用Person引用new创建对象
         */
        printName("杨幂",Person::new);
    }
}

数组的构造器引用

package com.asus.demo45;
/*
    一个创建数组的函数式接口
 */
@FunctionalInterface
public interface ArrayBuilder {
    //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
    int[] buildArray(int length);
}
package com.asus.demo45;
/*
    数组的构造器引用
 */
public class Test {
    /*
        定义一个方法,方法的参数传递创建数组的长度和ArrayBuilder接口
        方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
     */
    public static int[] createArray(int length,ArrayBuilder ab)
    {
        return ab.buildArray(length);
    }

    public static void main(String[] args) {
        //调用createArray()方法,传递数组的长度和Lambda表达式
        int[] array = createArray(3, len -> new int[len]);
        System.out.println(array.length);
        /*
            使用方法引用优化Lambda表达式
            已知创建的就是int[]数组
            数组的长度也是已知的
            就可以使用方法引用
            int[]引用new,根据参数传递的长度来创建数组
         */
        int[] array1 = createArray(6, int[]::new);
        System.out.println(array1.length);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值