函数式接口 ,Stream流

文件上传优化:

  文件名称需要优化:

服务端,保存文件的名称如果名称固定,那么最终会导致服务器硬盘,只会保留一个文件,对上传的文件名称优化

//文件名称定义规则
"beautiful"+System.currentTimeMillis()+new Randow().nextInt(1000000)+".jpg"
//可以保证下载的文件名不重复。

服务端接收文件的优化:

 服务器端接受一个文件之后就关闭了,后面的其他客户端无法上传文件,使用循环进行改进,可以不断地接收不同的客户端传输过来的文件。

//使用循环
while(true){
 
   Socket socket=serverSocket.accept();
   .....
   ....
   

}

 服务器端接收客户端文件的效率的优化:

服务器端,在接受文件的时候,加入某个客户端给你传一个大文件,此时就不能再接收其他用户的文件,所以可以用多线程技术优化接收效率。
 while(true){
  
    Socket socket=serverSocket.accept();

 //使用多线程技术,提高程序的效率
 //有一个客户端上传文件,就开启一个线程,完成文件的上传
   new Thread(new Runnable(){
 
    //重写run方法,设定线程任务
     @Override
     public void run(){

        //使用网络字节输入流对象
        InputStream  is=socket.getInputStream();
        // 指定一个目录
        File file=new File("D:\\upload");
        if(!file.exits()){
           file.mkdirs();
         //防止同名的文件被覆盖
        String filename="beautiful"+System.currentTimeMillis()+new 
               Randow().nextInt(1000000)+".jpg";
        //构建一个本地的文件字节输出流对象
           FileOutputStream  fos=new FileOutputStream(file+"\\"+filename);
        //一读一写完成文件的上传,最终把文件写入到服务器端的硬盘当中。
           ...

      }
   }).start();
}
//服务器不关闭

//serverSocket.close();


    

函数式接口:

   概念:函数式接口在java中指的是,有且仅有一个抽象方法的接口就称为函数式接口。

函数式接口,适用于函数式编程的在Java当中的函数式编程体现在Lambda表达式,所以函数式接口就是用来服务Lambad表达式的。只有确保接口当中有且仅有一个抽象方法,那么在java中的Lambda才能顺利的进行推导。

备注:“语法糖”是指使用更加便利方便,但是原理不变代码语法。就比如遍历集合的时候使用for -each其实底层使用的是迭代器。这便是"语法糖"。

格式:

   只有确保接口当中有且仅有一个抽象方法即可:
 

 修饰符   interface  InterfaceName{

   //只能定义一个抽象方法

  public abstract 返回值类型  方法名称(参数列表);

  //还可以定义其他的非抽象方法  

}    

例如:

public interface A{

 public abstrac void show01();
 public default void show02(){
    //...
 }
 //void show03();有且仅有一个抽象方法才成为函数式接口
}

@Functionallnterface注解

与@Override注解作用类似,java8中专门为函数式接口引入的一个新的注解@Functionallnterface,该注解主要定义在接口上。一旦在接口上使用该注解,编译器会强制检查该接口是不是一个函数式接口,该接口中是不是有且仅有一个抽象方法,如果不是,编译报错。

//函数式接口注解
@FunctionalInterface
//作用:可以检测接口是否为一个函数式接口  是:编译通过    否:编译失败
interface A {
    //定义一个抽象方法
    void method();

    // void  method2();
    default void me() {
        System.out.println("你好呀");
    }


}

自定义函数式接口的用途

  对于自定义的函数式接口,一般用于方法的参数和返回值上。

函数式编程:

能够在兼顾Java的面向对象特性的基础上,通过Lambda表达式与后面的方法引用,为开发者打开函数式编程的大门。

Lambda的延迟加载

有些场景的代码运行执行后,结果不一定会被使用到,从而造成性能的浪费,而Lambda表达式时延迟执行的,正好可以解决此问题,提升性能。

实际上使用内部类也可以达到这样的效果,只是将代码操作延迟到另外一个对象当中用过调用方法来完成。后面的代码的执行取决于前面的条件的判断结果。

      public static void main(String[] args) {
        // 定义一些日志信息
        String message1 = "执行mysqld.exe操作";
        String message2 = "执行java.exe操作";
        String message3 = "执行tomcat.exe操作";

        // 调用showLog方法,参数是一个函数式接口--BuildLogMessage接口,所以可以使用Lambda表达式
       /* showLog(2, () -> {
            // 返回一个拼接好的字符串
            return message1 + message2 + message3;
        });*/

        // 简化Lambda表达式
        /*
               使用Lambda表达式作为参数传递,
               只有满足条件,日志的等级小于等于3
                     才会调用此接口BuildLogMessage当中的方法buildLogMessage
                     才会进行字符串的拼接动作
               如果条件不满足,日志的等级大于3
                      那么BuildLogMessage接口当中的方法buildLogMessage也不会执行
                      所以拼接字符串的动作也不会执行
                      所以不会存在性能上的浪费。
         */
        showLog(4, () -> {
            System.out.println("前面的日志等级大于3,此处不执行!");
           return message1 + message2 + message3;
        });
  }

 

备注:实际上使用内部类也可以达到这样的效果,只是将代码操作延迟到另外一个对象当中通过调用方法来完成。

后面的代码的执行取决于前面的条件的判断结果。

使用Lambda作为方法的参数和返回值:

在Java当中,Lambda表达式是作为匿名内部类的替代品,如果一个方法的参数是一个函数式接口类型,那么可以使用Lambda表达式进行替代。

java.lang.Runnable接口就是一个函数式接口。代码如下:

public class Demo01Lambda{
  //定义一个方法,开启线程的方法

  public static void startThread(Runnable r){
    
      new Thread(r).start;
  } 
 


  public static void main(String[] args){
 
   startThread(()->{
   
     System.out.println("开启一个新的线程,线程任务被执行了!");
   });
    //优化Lambda
   
    startThread(()->System.out.println("开启新线程");
  }
}

如果一个方法的返回值类型是一个函数式接口,那么我们可以直接使用一个Lambda表达式。

java.util.Comparator接口是一个函数式接口;

代码如下:

public class Demo02Lambda{

   //定义一个方法,方法的返回值类型是一个函数式接口类型Comparator
  
    public static Comparator<String> createComparator(){
 
      //返回值就是一个函数式接口
        
     return new Comparator(){
     @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[] strs = {"aaa","s","bbb","dddd"};

    Arrays.sort(strs,createComparator());
  
    System.out.println(Arrays.toString(str));
  }
 

}

 

 常用的函数式接口:

 JDK提供了大量常用的函数式接口,丰富Lambda表达式的使用场景,他们主要在java.util.function包中被提供

Supplier接口

        java.util.function.Supplier接口,该接口有且仅有一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据。由于该接口是一个函数式接口,所以我们可以使用Lambda表达式来操作它。

       Supplier<T>接口被称为生产型接口,指定接口的泛型是什么类型,那么接口中的get()方法就会生产什么类型的数据。

       

 // 定义一个方法,方法的参数传递一个Supplier<T>接口,泛型指定String,get方法就会返回一个String
    public static String getString(Supplier<String> sup) {
        return sup.get();
    }
    // 定义一个方法,方法的参数传递一个Supplier<T>接口,泛型我指定为Integer,get方法就会返回一个int
    public static int getNum(Supplier<Integer> sup) {
        return sup.get();
    }
    public static void main(String[] args) {
        // 调用getString方法,方法的参数传递Supplier<T>是一个函数式接口,那么我们就可以使用Lambda
       /*  String str = getString(() -> {
            // 生产一个字符串并返回
            return "你好Java";
        });
        System.out.println(str);*/
        
        // 求一个int类型的数组中的最值
        int[] arr = {10,20,5,8,3,50};
        int maxNum = getNum(() -> {
            // 求出数组的最大值
            int max = arr[0];
            for (int i : arr) {
                // 判断
                if (max < i) {
                    max = i;
                }
            }
            return max;
        });
        // 输出最大值
        System.out.println(maxNum);// 50
    }

Consumer接口:

     java.util.function.Consumer<T>接口刚好和Supplier接口相反的,他不是用来生产一个数据,而是用来消费一个数据。

数据的类型由泛型来指定。

  accept方法:

   消费一个指定泛型的数据。

   代码如下:

        

*
 accept()方法,消费一个指定类型的数据
 Consumer接口是一个消费型接口,泛型指定什么数据类型,使用accept方法就消费什么类型的数据
 至于具体怎么消费,需要自定义(统计,求和,输出....)
 */
public class DemoConsumer {
    //定义一个方法,方法的参数传递一个Consumer<T>接口,传递一个字符串变量
    public static  void consumer(String string,Consumer<String> con){
        //使用消费型接口对象,消费传递的字符串值
        con.accept(string);
    }

    public static void main(String[] args) {
        //来调用消费方法consumer

        consumer("abcdsdgfsf", (name)-> System.out.println(
                new StringBuffer(name.toUpperCase()).reverse().toString()));
    }
}

默认的方法:andThen

如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现这样的效果,消费数据的时候,首先做一个消费的操作,再做一个消费的操作,实现组合。可以通过Consuner接口当中的默认方法:andThen来实现。

代码如下:

 public static void main(String[] args) {

        consumers("crazy-韩先森-加油奥里给", (name1) ->
           //消费规则
            //截取传入的字符串
            //final String s = name1.substring(0, 6);
            System.out.println(new String(name1.substring(0, 6).toString()))

        , (name2) -> {
            //定义消费的规则,分成字符串数组展示
            String[] strings = name2.split("-");
            System.out.println(Arrays.toString(strings));//{crazy,韩先森,加油奥里给}

        });

    }

   

    //定义一个方法,方法的参数传递一个字符串和两个Consumer<T>接口,Consumer接口的泛型指定为字符串
    public static void consumers(String s, Consumer<String> con1, Consumer<String> con2) {
        //
//        con1.accept(s);
//        con2.accept(s);
        //andThen连续消费   default Consumer<String> andThen
        con1.andThen(con2).accept(s);
        //规则con1连接con2,先执行con1消费数据,再执行con2数据
        /*
            crazy- 
            [crazy, 韩先森, 加油奥里给]
         */
    }

通过查看源码得知,andThen方法不允许传入一个null对象否则就会抛出空指针异常。

要想把两次消费的动作连接起来,需要传入两个Consumer接口,通过andThen方法一步一步执行消费动作。

练习:

/*
 练习:定义一个字符串数组,存储每一个人的信息:"张三,20,郑州市",存储5个人的信息。使用Consumer接口按照指定的格式进行打印输出:姓名:张三;年龄:20;地址:郑州市

 要求将打印姓名的动作作为第一个Consumer接口的规则

将打印年龄的动作作为第二个Consumer接口的规则

将打印地址的动作作为第三个Consumer接口的规则;
*/

 String[] s = {"张1,20,郑州市",
                "张2,21,郑州市1"
                , "张3,22,郑州市2"
                , "张4,23,郑州市3"
                , "张5,24,郑州市4"};
        //Lambda表达式表示
        consumer3(s, (name) -> System.out.print("姓名:" + name.split(",")[0] + ";  ")
                , (age) -> System.out.print("年龄:" + age.split(",")[1] + ";  ")
                , (address) -> System.out.println("地址:" + address.split(",")[2]));

    }

    public static void consumer3(String[] strings,
                                 Consumer<String> consumer,
                                 Consumer<String> consumer2,
                                 Consumer<String> consumer3) {
        for (String string : strings) {
            //先消費consumer再消费consumer2,再消费consumer3
            consumer.andThen(consumer2).andThen(consumer3).accept(string);
        }

    }

Stream流:

在java1.8中,由于Lambda表达式这种函数式编程,JDK引入了一个全新的概念“Stream流”,用于解决已有集合类库的一些弊端的。

给定你一些集合的数据

public class DemoStream{
  public static  void main(String[] args){
  
    //构建一个集合
   List<String> list = new ArrayList<String>();
   list.add("123465");
   list.add("acb1353");
   list.add("abd144");
   list.add("afg125");
   list.add("ahb175");
   list.add("asd1657");
   //需要字符串包含数字2的元素取出来
   List<String> list2 = new ArrayList<String>();
   for(String str:list){
   
   if(str.contains("2")){    
      
       list2.add(str);
 
    }
   //在结果字符串集合中把字母串长度不能超过6个的元素取出来.
   List<String> list3 = new ArrayList<String>();
   for(String str:list2){
   
     if(str.length()<=6){    
      
        list3.add(str);
 
     }
  
   }
    //遍历查看最终想要的元素集合
   for(String str: list3){
   
   System.out.println(str);

   }
 


 }
}

 当我们需要集合当中的元素进行操作的时候,总是需要对集合循环遍历,再次循环遍历...一定要这样做吗?

  不一定,它只是用来找到你需要元素的一种方式,并不是目的。目的是想要取出想要的元素并且循环打印展示出来。以往的方式就是每次循环都需要从头开始遍历,下一次循环还是从头开始。

Stream流对象中的API方法。

java1.8可以使用Lambda表达式来优化你遍历集合的方式。

public class DemoStream{
  public static  void main(String[] args){
  
    List<String> list = new ArrayList<String>();
        list.add("123465");
        list.add("acb1353");
        list.add("abd144");
        list.add("afg125");
        list.add("ahb175");
        list.add("asd1657");
        //需要字符串包含数字2的元素取出来

        //在结果字符串集合中把字母串长度不能超过6个的元素取出来.

        //遍历查看最终想要的元素集合
        list.stream().filter(str->str.contains("2")).filter(str->str.length() 
            <=6).forEach(str->System.out.println(str));

 }
}

 一般我们把流式思想称之为"生产流水线"。

  流式思想概述:

     整体来看,流式思想类似于工厂中的"生产流水线"。

当需要对多个元素进行操作的时候,尤其是多步操作,考虑到性能以及遍历性,首先需要考虑一个“模型”步骤方案。然后按照设计的方案去执行。

比如你做某种类型的商品机进行操作,你需要进行过滤,映射,跳过,计数等操作。这也是我们对集合中的元素操作的步骤,这一套步骤我们称之为一种处理方案,而方案就是一种“函数模型”。方案中操作的每一个步骤,我们都可以称之为"流"。调用指定的API方法,从一个流中转换为另一个流。都有对应的API方法,filter,map,skip,count都是对函数模型进行操作。

当我们使用一个流的时候。通常包含三个基本步骤:

1.获取一个数据源        2.进行数据转换       3.执行需要的操作获取想要的操作结果。每次转换原有的Stream对象,会返回新的Stream对象。这样我们就可以像链条一样进行操作。

Stream流和以往的Collection集合有所不同。Stream操作有两个基础的特征:

     1.中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格,对中间进行优化,比如可以进行延迟执行和短路。

     2.内部迭代:以前遍历集合都是迭代器或者增强for循环,显示的在集合外部进行迭代,这叫做外部迭代。Stream流提供了内部迭代的方法,这个流可以直接调用遍历的方法。

    Stream流其实是一个集合元素的函数模型,不是集合,也不是数据结构,其本身并不存储任何元素或者是地址值。

Stream流,是一个来自数据源的元素队列:

    元素是特定类型的对象,形成一个队列。java中的Stream并不会存储元素,而是按需计算。

    数据源   流的来源,可以是集合,也可以是数组等容器。

获取流对象

 java.util.stream.Stream<T>是JDK1.8引入的新特性,较为常用的接口(本身并不是函数式接口)

获取一个流对象,有以下常见的操作:

      所有的Collection集合都可以通过stream()默认的方法来获取。

      Stream接口里面含有一个静态方法of也可以获取对应的流对象。

根据Collection集合或者是of来获取流对象

 只要是Collection集合的实现类或者子接口都可以调用stream默认方法获取流对象

public static void main(String[] args) {
		// 把集合转换为Stream流
		List<String> list = new ArrayList<>();
		Stream<String> stream1 = list.stream();
		
		HashSet<Integer> set = new HashSet<>();
		Stream<Integer> stream2 = set.stream();
		
		HashMap<String, String> map = new HashMap<>();
		// map中的key存储到一个set中
		Set<String> keySet = map.keySet();
		Stream<String> stream3 = keySet.stream();
		
		// 把map中的value值存储到一个Collection集合中
		Collection<String> values = map.values();
		Stream<String> stream4 = values.stream();
		
		// 把map中的key和value值一起存储到entry(键与值的映射)中
		Set<Entry<String,String>> entrySet = map.entrySet();
		Stream<Entry<String, String>> stream5 = entrySet.stream();
		
		// 把数组转换为Stream流
		Stream<Integer> stream6 = Stream.of(1,2,3,4,5,6);
		
		stream6.filter(num -> num > 3).filter(num -> num % 2 == 0).forEach(num ->System.out.println(num));
		
		// 可变参数是一个数组
		String[] arr = {"a","b","c","d"};
		Stream<String> stream7 = Stream.of(arr);
	}

  Stream 流中的常用方法:

   流模型中的操作很多,大致上可以把其中的api方法分成两部分:

  延迟方法:返回值类型都是Stream接口自身,因此可以支持链式操作。

  终结方法:返回值就不是Stream接口自身,因此不能再进行链式操作。比如:count方法和forEach方法

 forEach方法

void forEach(Consumer<T> consumer);//借助于该函数式接口中的方法accept方法
//Consumer<T>是一个消费性接口,用来消费一个指定泛型的数据

代码如下:

//获取一个数据
		Stream<String> stream6 = Stream.of("abc","abnc","abmc","jabc","anbc");
		//转换数据
		//执行操作获取想要的结果
		stream6.forEach(str->{
			if(str.contains("n")){
				System.out.println(str);
			}
		});

过滤:filter

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

Stream<T> filter(Predicate<? super T>predicate)返回由与此给定谓词匹配的此流的元素组成的流。
//借助于Predicate函数式接口当中的抽象方法 test(T t) 对数据进行过滤

该方法接收一个函数式接口Predicate,可以使用Lambda表达式进行条件的筛选。

Predicate接口:

java.util.stream.Preticate函数式接口,其中唯一的抽象方法

boolean  test(T t),该方法会返回布尔类型值,代表指定的条件是否满足,如果条件满足返回true,那么Stream流的方法filter将集合或者数组其中的元素保留下来,如果条件不满足返回false,那么filter方法会舍弃该元素。

public static void main(String[] args) {
		// 1、 准备一个数据源
		// 获取该数据源
		String[] arr = {"小孙","小王","小赵","老王","涂少","老刘"};
		// 2. 数据转换
		// 使用Stream流中的方法filter,对姓涂的人过滤掉
		Stream<String> stream = Stream.of(arr);
		Stream<String> stream2 = stream.filter(name -> !name.contains("涂"));
		Stream<String> stream3 = stream2.filter(name -> name.startsWith("小"));
		stream3.forEach(name ->System.out.println(name));
		stream2.filter(name -> !name.contains("少")).forEach(name ->System.out.println(name));
		 
		/* Stream流属于管道流,每次只能被消费一次
		 * 第一个Stream流调用完毕后,数据就会被转换到下一个Stream上
		 * 而这时第一个Stream流已经使用完毕,就会关闭了。
		 * 所以第一个Stream就不能再调用方法了。
		 * 如果你强制调用方法,程序就会抛出非法状态异常
		 * java.lang.IllegalStateException: stream has already been operated upon or closed
		 * stream.filter(name -> !name.contains("涂"))
		      .filter(name -> name.startsWith("小"))
		      .forEach(name ->System.out.println(name));
		*/
	}

映射:map

如果你需要将流中的数据映射到另外一个流中,可以使用map方法。

<R> Stream<R> map(Function<? super T,? extends R> mapper)返回由给定函数应用于此流的元素的结果组成的流。

该方法接受一个函数式接口Function作为方法参数。可以将当前流中的T数据转换成另外一种R类型的数据。

Function接口

java.util.stream.Function函数式接口,其中唯一的抽象方法:

R apply(T t)
//可以将一种T类型的数据转换成R类型的数据,那么这种转换的动作,我们称之为"映射";
public static void name2() {
		// 1.准备一个数据源
		// 获取数据
		// 把String字符串的整数--》int 类型的整数
		Stream<String> stream = Stream.of("123", "124", "1243", "1423", "1263");
		// 2.数据转换,把字符串类型的数据转换成int类型的数据 由于Function是一个函数式接口,所以可以使用Lambda表达式
		// apply(T t)
		Stream<Integer> map=stream.map((String str)->{
			return Integer.valueOf(str);
		});
		//Stream<Integer> map = stream.map(str -> Integer.valueOf(str));
		
		//遍历
		map.forEach(num -> System.out.println(num));
	}

统计个数:count

可以像Collection集合中的size()一样,统计流中的元素个数,通过count方法来实现。

//返回此流中的元素个数
long count();

该方法返回一个long类型的值代表流中的元素个数(区别于size()(返回值为int值))

public static void name(){
   

   Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
   //统计个数
  long count = stream.count();
  System.out.println(count);//6



}

取用流中前几个元素的方法 :limit

limit()方法可以对流中的数据进行限制-->截取操作,需要一个参数max,设定取用流中前max个数。

Stream<T> limit(long maxSize)//返回由此流的元素组成的流,截取长度不能超过maxSize.

参数是一个long类型的,截取的长度不能超过流中最大元素个数;否则不进行操作。

public void demo3(){
  
 
  //准备一个数据
//获取数据
 Stream<Integer> stream = Stream.of(12,13,23,43,24,67);
 
 //想要截取流中的前五个元素
Stream<Integer> stream2 = stream.limit(3);
 //查看流中的元素个数
System.out.println(stream2.count());//3
}

跳过前几个:skip

如果你希望跳过前几个元素,取用后几个元素,可以使用skip方法来实现。

Stream<T> skip(long n)//在丢弃流的第一个n元素后,返回由该流的n元素组成的流。

 如果流中的当前个数小于n,你将会的得到一个长度为0的空流,反之流中的个数大于n,则会跳过前n个元素。

public static void name3() {
		//1.获取数据
		String[] source = {"123","124","235","acd","gh","gjk"};
		Stream<String> stream = Stream.of(source);
		//跳过前三个元素
		Stream<String> stream2= stream.skip(3);
		//Stream<String> stream2= stream.skip(source.length);//空流
		//
		stream2.forEach(name->System.out.println(name));//"acd","gh","gjk"
		
//		long count = stream2.count();
//		System.out.println(count);
		

}

 组合:concat

如果有两个流,希望合并成一个流,那么就可以使用concat静态方法

//创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。
static <T> Stream<T> concat(Stream<? extends T>) a,Stream<? extends T> b)

 

public void demo(){
  //准备两个流
  //获取两次数据流
  Stream<Integer> stream  = Stream.of(12,13,45,56,78,1);
  Stream<Integer> stream2 = Stream.of(1,3,5,6,7);
  //合并流
  Stream<Integer> stream3 = Stream.concat(stream,stream2);
  stream3.forEach(name->System.ouy.println(name));
 //结果:12,13,45,56,78,1,1,3,5,6,7
}

方法引用:

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值