【Java笔记】基础学习笔记汇总(下)

【Java笔记】基础学习笔记汇总(下)

原本笔记都是手写的,为了之后方便保存和查阅,还是下定决心,把他敲出来正好作为一篇博客的内容。也借这个机会,好好的复习一遍Java基础知识。这是下半部分。

自学材料:黑马程序员Java全套视频

1. 网络编程三要素

  • 协议(TCP/UDP)
  • IP地址(IPv4/IPv6)
  • 端口号(取值范围是0~65535;其中,0~1023之间的端口号用于一些知名的网络服务和应用)

常用的端口号:

  • 网络端口:80
  • 数据库:mysql = 3306;oracle:1521
  • Tomcat服务器:8080

2. TCP通信

面向连接的通信,客户端和服务端必须经过3次握手,建立逻辑链接,才能安全通信

3. 客户端 java.net.Socket

该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

套接字:包含了IP地址和端口号的网络单位


构造方法:

  • public Socket(String host, int port):创建一个套接字对象并将其连接到指定的主机上的指定端口

参数:

  • String host:服务器的主机名称/服务器的IP地址
  • int port:服务器的端口号

成员方法:

  • public OutputStream getOutputStream():返回此套接字的输出流

如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道;
关闭生成的OutputStream也将关闭相关的Socket

  • public InputStream getInputStream():返回此套接字的输入流

如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道;
关闭生成的InputStream也将关闭相关的Socket

  • public void close():关闭此套接字

一旦一个socket被关闭,它不可再使用;
关闭此socket也将关闭相关的InputStream和OutputStream

  • public void shutdownOutput():禁用此套接字的输出流

任何先前写出的数据将被发送,随后终止输出流


使用步骤:

1)创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号

2)使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象

3)使用网络字节输出流OutputStream对象中的write()方法,给服务器发送数据

4)使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象

5)使用网络字节输入流InputStream对象中的read()方法,读取服务器回写的数据

6)释放Socket资源


注意事项:

1)客户端和服务端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象

2)当我们创建客户端对象Socket的时候,就会去请求服务器,和服务器经过3次握手建立连接通路

这时若服务器没启动,就会抛出异常;若服务器已启动,就可进行交互

4. 服务端 java.net.ServerSocket

这个类实现了服务器套接字,接收服务端的请求,读取客户端发送的数据,给客户端回写数据

构造方法:

  • public ServerSocket(int port):创建并绑定到特定端口号的服务器套接字

注意:

服务器需要明确一件事情,必须得知道是哪个客户端请求的服务器:使用accept()方法来获取


成员方法:

  • public Socket accept():侦听并接受连接,返回一个Socket对象,用于和客户端实现通信。该方法
    会一直阻塞直到建立连接

实现步骤:

1)创建服务器ServerSocket对象和系统要指定的端口号

2)使用ServerSocket对象中的方法accept(),获取请求的客户端对象Socket

3)使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象

4)使用InputStream对象中的方法read(),读取客户端发送的数据

5)使用Socket对象中的方法getOutputStream()获取OutputStream对象

6)使用OutputStream对象中的方法write(),给客户端回写数据

7)释放资源(Socket和ServerSocket)

5. TCP网络通信编程综合案例:文件上传

流程:

1)客户端使用本地的字节输入流,读取要上传的文件

2)客户端使用网络字节输出流,把读取的文件上传到服务端

3)服务端使用网络字节输入流,读取客户端上传的文件

4)服务端使用本地字节输出流,把读取的文件,保存到服务端硬盘上

5)服务端使用网络字节输出流,给客户端回写一个上传成功

6)客户端使用网络字节输入流,读取服务端回写的数据

优化方法:

1)接收并保存文件命名规则(随机数或毫秒值或。。。)

2)死循环,让服务端一直处于监听状态

3)使用多线程技术,提高程序的效率:有一个的客户端上传文件,就开启一个线程,完成文件的上传


客户端代码示例:

public class FileUpload_Client {
    public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 创建输入流,读取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
        // 1.2 创建输出流,写到服务端
        Socket socket = new Socket("localhost", 6666);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        //2.写出数据.
        byte[] b  = new byte[1024 * 8 ];
        int len ;
        while (( len  = bis.read(b))!=-1) {
            bos.write(b, 0, len);
        }
      	// 关闭输出流,通知服务端,写出数据完毕
        socket.shutdownOutput();
        System.out.println("文件发送完毕");
        // 3. =====解析回写============
        InputStream in = socket.getInputStream();
        byte[] back = new byte[20];
        in.read(back);
        System.out.println(new String(back));
        in.close();
        // ============================

        // 4.释放资源
        socket.close();
        bis.close();
    }
}

服务端代码示例:

public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器 启动.....  ");
        // 1. 创建服务端ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        // 2. 循环接收,建立连接
        while (true) {
            Socket accept = serverSocket.accept();
          	/*
          	3. socket对象交给子线程处理,进行读写操作
               Runnable接口中,只有一个run方法,使用lambda表达式简化格式
            */
            new Thread(() -> {
                try (
                    //3.1 获取输入流对象
                    BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                    //3.2 创建输出流对象, 保存到本地 .
                    FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
                    BufferedOutputStream bos = new BufferedOutputStream(fis);
                ) {
                    // 3.3 读写数据
                    byte[] b = new byte[1024 * 8];
                    int len;
                    while ((len = bis.read(b)) != -1) {
                        bos.write(b, 0, len);
                    }

                    // 4.=======信息回写===========================
                    System.out.println("back ........");
                    OutputStream out = accept.getOutputStream();
                    out.write("上传成功".getBytes());
                    out.close();
                    //================================

                    //5. 关闭 资源
                    bos.close();
                    bis.close();
                    accept.close();
                    System.out.println("文件上传已保存");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

6. 函数式接口

有且只有一个抽象方法的接口,称之为函数式接口。当然接口中可以包含其他方法(默认、静态、私有)

只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导


注解:@FunctionalInterface

JDK1.8引入的新注解;编译器会检查该接口是否确实有且仅有一个抽象方法,即检测接口是不是一个函数式接口


函数式接口的使用场景:作为方法的参数和返回值类型(两种)

例如:

public class Demo09FunctionalInterface {
    // 使用自定义的函数式接口作为方法参数
    private static void doSomething(MyFunctionalInterface inter) {
        inter.myMethod(); // 调用自定义的函数式接口方法
    }
    public static void main(String[] args) {
        // 调用使用函数式接口的方法
        doSomething(() ‐> System.out.println("Lambda执行啦!"));
    }
}

当函数式接口作为方法的参数时:

  • 可以传递入这个接口的实现类对象
  • 可以传递入这个接口的匿名内部类
  • 可以使用Lambda表达式

7. Lambda的特性:延迟执行/加载

代码示例:性能浪费的日志案例

这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接

//优化前
public class Demo01Logger {
    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);
    }
}

//优化后
@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}
public class Demo02LoggerLambda {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
            System.out.println(builder.buildMessage());
        }
    }
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1, () ‐> msgA + msgB + msgC );
    }
}

8. 常用的函数式接口1:java.util.function.Supplier< T >

接口仅包含一个无参的方法:

  • T get(),用来获取一个泛型参数指定类型的对象数据

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

例:使用Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值

public class Demo02Test {
    //定一个方法,方法的参数传递Supplier,泛型使用Integer
    public static int getMax(Supplier<Integer> sup){
        return sup.get();
    }
    public static void main(String[] args) {
        int arr[] = {2,3,4,52,333,23};
        //调用getMax方法,参数传递Lambda
        int maxNum = getMax(()‐>{
            //计算数组的最大值
            int max = arr[0];
            for(int i : arr){
                if(i>max){
                    max = i;
                }
            }
            return max;
        });
        System.out.println(maxNum);
    }
}

9. 常用的函数式接口2:java.util.function.Consumer< T >

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

接口中包含抽象方法

  • void accept(T t),意为消费一个指定泛型的数据

代码示例:

import java.util.function.Consumer;
public class Demo09Consumer {
    private static void consumeString(Consumer<String> function) {
        function.accept("Hello");
    }
    public static void main(String[] args) {
        consumeString(s ‐> System.out.println(s));
    }
}

Comsumer接口的默认方法:

  • andThen

作用:需要2个Consumer接口,可以把两个Consumer接口组合在一起,对数据进行消费;谁写在前面,谁先消费

代码示例:

先输出大写HELLO,然后输出小写hello

import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
    private static void consumeString(Consumer<String> one, Consumer<String> two) {
        one.andThen(two).accept("Hello");
    }
    public static void main(String[] args) {
        consumeString(
            s ‐> System.out.println(s.toUpperCase()),
            s ‐> System.out.println(s.toLowerCase()));
    }
}

例:按照格式“ 姓名:XX。性别:XX。”的格式将信息打印出来。打印姓名的动作作为第一个Consumer接口的Lambda实例,打印性别的动作作为第二个Consumer接口的Lambda实例

import java.util.function.Consumer;
public class DemoConsumer {
    public static void main(String[] args) {
        String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
        printInfo(s ‐> System.out.print("姓名:" + s.split(",")[0]),
                  s ‐> System.out.println("。性别:" + s.split(",")[1] + "。"),
                  array);
    }
    private static void printInfo(Consumer<String> one, Consumer<String> two, String[]array) {
        for (String info : array) {
            one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
        }
    }
}

10. 常用的函数式接口3:java.util.function.Predicate< T >

对于某种数据类型进行判断,结果返回一个boolean值

Predicate接口中包含一个抽象方法:

  • boolean test(T t):用来对指定数据类型进行条件判断

代码示例:

import java.util.function.Predicate;
public class Demo15PredicateTest {
    private static void method(Predicate<String> predicate) {
        boolean veryLong = predicate.test("HelloWorld");
        System.out.println("字符串很长吗:" + veryLong);
    }
    public static void main(String[] args) {
        method(s ‐> s.length() > 5);
    }
}

Predicate接口中的默认方法(与或非):

  • and(...):将两个Predicate判断条件 使用“与”逻辑连接起来实现“并且”的效果时
  • or(...):“或”逻辑
  • negate(...):“非”逻辑

例:如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

//and代码示例
import java.util.function.Predicate;
public class Demo16PredicateAnd {
    private static void method(Predicate<String> one, Predicate<String> two) {
        boolean isValid = one.and(two).test("Helloworld");
        System.out.println("字符串符合要求吗:" + isValid);
    }
    public static void main(String[] args) {
        method(s ‐> s.contains("H"), s ‐> s.contains("W"));
    }
}

//negate代码示例
import java.util.function.Predicate;
public class Demo17PredicateNegate {
    private static void method(Predicate<String> predicate) {
        boolean veryLong = predicate.negate().test("HelloWorld");
        System.out.println("字符串很长吗:" + veryLong);
    }
    public static void main(String[] args) {
        method(s ‐> s.length() < 5);
    }
}

例:数组当中有多条“姓名+性别”的信息,通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:1)必须为女生;2)姓名为4个字。

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class DemoPredicate {
    public static void main(String[] args) {
        String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
        List<String> list = filter(array,
                                   s ‐> "女".equals(s.split(",")[1]),
                                   s ‐> s.split(",")[0].length() == 4);
        System.out.println(list);
    }
    private static List<String> filter(String[] array, Predicate<String> one,
                                       Predicate<String> two) {
        List<String> list = new ArrayList<>();
        for (String info : array) {
            if (one.and(two).test(info)) {
                list.add(info);
            }
        }
        return list;
    }
}

11. 常用的函数式接口4:java.util.function.Function< T,R >

接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件

Function接口中最主要的抽象方法为:

  • R apply(T t):根据类型T的参数获取类型R的结果

使用的场景例如:将String类型转换为Integer类型

Function接口中有一个默认方法

  • andThen:用来进行组合操作,使用方法类似于Consumer接口中的andThen

注意:

  • Function的前置条件泛型和后置条件泛型可以相同

代码示例:第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过andThen 按照前后顺序组合到了一起

import java.util.function.Function;
public class Demo12FunctionAndThen {
    private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
        int num = one.andThen(two).apply("10");
        System.out.println(num + 20);
    }
    public static void main(String[] args) {
        method(str‐>Integer.parseInt(str)+10, i ‐> i *= 10);
    }
}

例:String str = "赵丽颖,20":1)将字符串截取数字年龄部分,得到字符串;2)将上一步的字符串转换成为int类型的数字;3)将上一步的int数字累加100,得到结果int数字

import java.util.function.Function;
public class DemoFunction {
    public static void main(String[] args) {
        String str = "赵丽颖,20";
        int age = getAgeNum(str, s ‐> s.split(",")[1],
                            s ‐>Integer.parseInt(s),
                            n ‐> n += 100);
        System.out.println(age);
    }
    private static int getAgeNum(String str, Function<String, String> one,
                                 Function<String, Integer> two,
                                 Function<Integer, Integer> three) {
        return one.andThen(two).andThen(three).apply(str);
    }
}

12. JDK1.8新出现的 Stream流/流式思想

Stream流主要是对集合的操作

关注的是做什么,而不是怎么做

例:直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        list.stream()
            .filter(s ‐> s.startsWith("张"))
            .filter(s ‐> s.length() == 3)
            .forEach(System.out::println);
    }
}

Stream(流)是一个来自数据源的元素队列

  • Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源:可以是集合,数组等

Stream操作还有2个基础的特征:

  • Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道
  • 内部迭代:以前对集合遍历都是通过Iterator或者增强for的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法

使用一个流通常包括三个步骤:

  • 获取一个数据源(source)
  • 数据转换,转换成流
  • 执行操作获取想要的结果,每次转换,原有Stream对象不改变,返回一个新的Stream对象,这就允许对其操作可以像链条一样排列,变成一个管道

13. 获取流 java.util.stream.Stream< T >

是JDK1.8新加入的最常用的流接口。(这并不是一个函数式接口)

  • 获取流,有以下2种常用的方式:

1)所有的Collection集合都可以通过stream默认方法获取流:default Stream < E > stream()
2)Stream接口的静态方法of可以获取 数组 对应的流:static < T > Stream< T > of(T...values)

of方法的参数是一个可变参数,所以支持数组。


方法一:

1)Collection获取流

java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流

代码示例:

import java.util.*;
import java.util.stream.Stream;
public class Demo04GetStream {
    public static void main(String[] args) {
        //list集合
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();
        
        //Set集合
        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();
        
        //Vector集合
        Vector<String> vector = new Vector<>();
        Stream<String> stream3 = vector.stream();
    }
}

2)Map获取流

java.util.Map接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况

代码示例:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        
        //键获取流
        Stream<String> keyStream = map.keySet().stream();
        //值获取流
        Stream<String> valueStream = map.values().stream();
        //键值对获取流
        //Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }
}

方法二:

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream 接口中提供了静态方法of

代码示例:

import java.util.stream.Stream;
public class Demo06GetStream {
    public static void main(String[] args) {
        String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
        Stream<String> stream = Stream.of(array);
    }
}

14. Stream流中的常用方法

这些方法可以被分成两种:

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

1)void forEach(Consumer< ? super T> action)

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理

注意:

  • forEach方法,用来遍历流中的数据
  • 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法

代码示例:

import java.util.stream.Stream;
public class Demo12StreamForEach {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
        stream.forEach(name‐> System.out.println(name));
    }
}

2)Stream< T > filter(Predicate<? super T> predicate)

该接口接收一个Predicate函数式接口参数作为筛选条件,用于对Stream流中的数据进行过滤

如果函数式接口返回结果为true,那么Stream流的filter方法将会留用元素;如果结果为false,那么filter方法将会舍弃元素

代码示例:

import java.util.stream.Stream;
public class Demo07StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
        Stream<String> result = original.filter(s ‐> s.startsWith("张"));
    }
}

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

将流中的元素映射到另一个流中

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流

代码示例:

//map方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer类对象)
import java.util.stream.Stream;
public class Demo08StreamMap {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("10", "12", "18");
        Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
    }
}

4)long count()

用于统计Stream流中元素的个数

注意:

  • count()方法是一个终结方法,返回值是一个long类型的整数
  • 所以不能再继续调用Stream流中的其他方法

代码示例:

import java.util.stream.Stream;
public class Demo09StreamCount {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
        Stream<String> result = original.filter(s ‐> s.startsWith("张"));
        System.out.println(result.count()); // 2
    }
}

5)Stream< T > limit(long maxSize)

对流进行截取,只取用前n个

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作

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

代码示例:

import java.util.stream.Stream;
public class Demo10StreamLimit {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
        Stream<String> result = original.limit(2);
        System.out.println(result.count()); // 2
    }
}

6)Stream< T > skip(long n)

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

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流

代码示例:

import java.util.stream.Stream;
public class Demo11StreamSkip {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
        Stream<String> result = original.skip(2);
        System.out.println(result.count()); // 1
    }
}

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

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

注意:这是一个静态方法,与java.lang.String当中的concat方法是不同的

代码示例:

import java.util.stream.Stream;
public class Demo12StreamConcat {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("张无忌");
        Stream<String> streamB = Stream.of("张翠山");
        Stream<String> result = Stream.concat(streamA, streamB);
    }
}

小练习代码示例:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class DemoStreamNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        // ...
        List<String> two = new ArrayList<>();
        // ...
        // 第一个队伍只要名字为3个字的成员姓名;
        // 第一个队伍筛选之后只要前3个人;
        Stream<String> streamOne = one.stream().filter(s ‐> s.length() == 3).limit(3);
        // 第二个队伍只要姓张的成员姓名;
        // 第二个队伍筛选之后不要前2个人;
        Stream<String> streamTwo = two.stream().filter(s ‐> s.startsWith("张")).skip(2);
        // 将两个队伍合并为一个队伍;
        // 根据姓名创建Person对象;
        // 打印整个队伍的Person对象信息。
        Stream.concat(streamOne,streamTwo).map(Person::new).forEach(System.out::println);
    }
}

15. 方法引用符

双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者

语义分析:

  • Lambda表达式写法:s -> System.out.println(s);
  • 方法引用写法:System.out::println

第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理
第二种等效写法的语义是指:直接让System.out中的println方法来取代Lambda

两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁

注意:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

16. 方法引用得各种用法

1)通过对象名引用成员方法

使用前提是对象名已经存在,成员方法也已经存在

//定义成员方法的一个类
public class MethodRefObject {
    public void printUpperCase(String str) {
        System.out.println(str.toUpperCase());
    }
}

//函数接口的定义
@FunctionalInterface
public interface Printable {
    void print(String str);
}

//那么当需要使用这个printUpperCase成员方法来替代Printable接口的Lambda的时候,已经具有了MethodRefObject类的对象实例,则可以通过对象名引用成员方法
public class Demo04MethodRef {
    private static void printString(Printable lambda) {
        lambda.print("Hello");
    }
    public static void main(String[] args) {
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCase);
    }
}

2)通过类名称引用静态成员方法

前提是类已经存在,静态方法也已经存在

由于在java.lang.Math类中已经存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。

//函数式接口的定义
@FunctionalInterface
public interface Calcable {
    int calc(int num);
}

//法一:Lambda表达式的写法
public class Demo05Lambda {
    private static void method(int num, Calcable lambda) {
        System.out.println(lambda.calc(num));
    }
    public static void main(String[] args) {
        method(‐10, n ‐> Math.abs(n));
    }
}

//法二:方法引用的写法
//两个方法等效
public class Demo06MethodRef {
    private static void method(int num, Calcable lambda) {
        System.out.println(lambda.calc(num));
    }
    public static void main(String[] args) {
        method(‐10, Math::abs);
    }
}

3)使用super引用父类的成员方法

前提是super是已经存在的,父类的成员方法也已经存在

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代

//函数式接口的定义
@FunctionalInterface
public interface Greetable {
    void greet();
}

//父类的定义
public class Human {
    public void sayHello() {
        System.out.println("Hello!");
    }
}

//子类的用法
public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }
    //定义方法method,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }
    public void show(){
        //调用method方法,使用Lambda表达式
        method(()‐>{
            //创建Human对象,调用sayHello方法
            new Human().sayHello();
        });
        //简化Lambda
        method(()‐>new Human().sayHello());
        //使用super关键字代替父类对象
        method(()‐>super.sayHello());
    }
}

//如果使用方法引用来调用父类中的sayHello方法会更好
public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }
    //定义方法method,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }
    public void show(){
        method(super::sayHello);
    }
}

4)使用this引用本类的成员方法

前提是this已经存在,本类的成员方法也已经存在

//函数式接口的定义
@FunctionalInterface
public interface Richable {
    void buy();
}

//原始写法
public class Husband {
    private void marry(Richable lambda) {
        lambda.buy();
    }
    public void beHappy() {
        marry(() ‐> System.out.println("买套房子"));
    }
}

//如果这个Lambda表达式的内容已经在本类当中存在了,则可以对Husband丈夫类进行修改
public class Husband {
    private void buyHouse() {
        System.out.println("买套房子");
    }
    private void marry(Richable lambda) {
        lambda.buy();
    }
    public void beHappy() {
        marry(() ‐> this.buyHouse());
    }
}

//如果希望取消掉Lambda表达式,用方法引用进行替换,则更好的写法为:
public class Husband {
    private void buyHouse() {
        System.out.println("买套房子");
    }
    private void marry(Richable lambda) {
        lambda.buy();
    }
    public void beHappy() {
        marry(this::buyHouse);
    }
}

5)类的构造器(构造方法)引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示

//创建一个类
public class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

//用来创建Person对象的函数式接口
public interface PersonBuilder {
    Person buildPerson(String name);
}

//要使用这个函数式接口,可以通过Lambda表达式:
public class Demo09Lambda {
    public static void printName(String name, PersonBuilder builder) {
        System.out.println(builder.buildPerson(name).getName());
    }
    public static void main(String[] args) {
        printName("赵丽颖", name ‐> new Person(name));
    }
}

//通过构造器引用,有更好的写法:
public class Demo10ConstructorRef {
    public static void printName(String name, PersonBuilder builder) {
        System.out.println(builder.buildPerson(name).getName());
    }
    public static void main(String[] args) {
        printName("赵丽颖", Person::new);
    }
}

6)数组的构造器的引用

前提是要创建的数组的长度已知

数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同

//函数式接口的定义
@FunctionalInterface
public interface ArrayBuilder {
    int[] buildArray(int length);
}

//在应用该接口的时候,可以通过Lambda表达式:
public class Demo11ArrayInitRef {
    private static int[] initArray(int length, ArrayBuilder builder) {
        return builder.buildArray(length);
    }
    public static void main(String[] args) {
        int[] array = initArray(10, length ‐> new int[length]);
    }
}

//更好的写法是使用数组的构造器引用:
public class Demo12ArrayInitRef {
    private static int[] initArray(int length, ArrayBuilder builder) {
        return builder.buildArray(length);
    }
    public static void main(String[] args) {
        int[] array = initArray(10, int[]::new);
    }
}

17. Junit单元测试

  • 测试分类

    1)黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。

    2)白盒测试:需要写代码的。关注程序具体的执行流程。

  • Junit使用:白盒测试

    • 步骤:

      1)定义一个测试类(测试用例)

      • 建议:
        • 测试类名:被测试的类名Test CalculatorTest
        • 包名:xxx.xxx.xx.test cn.itcast.test

      2)定义测试方法:可以独立运行

      • 建议:
        • 方法名:test测试的方法名 testAdd()
        • 返回值:void
        • 参数列表:空参

      3)给方法加@Test

      4)导入junit依赖环境

    • 判定结果:

      • 红色:失败
      • 绿色:成功
      • 一般我们会使用断言操作来处理结果
        • Assert.assertEquals(期望的结果,运算的结果);
    • 补充:

      • @Before:
        • 修饰的方法会在测试方法之前被自动执行
        • 用于资源申请
      • @After:
        • 修饰的方法会在测试方法执行之后自动被执行
        • 用于释放资源

18. Java代码在计算机中经历的三个阶段:

19. 反射:框架设计的灵魂

  • 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码

  • 反射:将类的各个组成部分封装为其他对象,这就是反射机制

    • 好处:

      1)可以在程序运行过程中,操作这些对象。

      2)可以解耦,提高程序的可扩展性。


  • 获取Class对象的方式:

    1)Class.forName("全类名"):将字节码文件加载进内存,返回Class对象

    • 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
    • 处于第一阶段:source源代码阶段

    2)类名.class:通过类名的属性class获取

    • 多用于参数的传递
    • 处于第二阶段:Class类对象阶段

    3)对象.getClass():getClass()方法在Object类中定义着。

    • 多用于对象的获取字节码的方式
    • 处于第三阶段:Runtime阶段
  • 注意:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。

应用举例:

//法一:
Class cls1 = Class.forName("cn.itheima.domain.Person");

//法二:
Class cls2 = Person.class;

//法三:
Person p = new Person();
Class cls3 = p.getClass();

  • Class对象功能:

    • 获取功能:

      1)获取成员变量们

      • Field[] getFields():获取所有public修饰的成员变量
      • Field getField(String name):获取指定名称的 public修饰的成员变量
      • Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
      • Field getDeclaredField(String name)

      对于Filed成员变量的操作:

      (1)对于访问private修饰符时,需要忽略访问权限修饰符的安全检查,需要加一行代码。

      忽略访问权限修饰符的安全检查:。。.setAccessible(true); //暴力反射

      (2)设置值:void set(Object obj, Object value)

      (3)获取值:get(Object obj)


      2)获取构造方法们

      • Constructor< ? >[] getConstructors()
      • Constructor< T > getConstructor(类<?>... parameterTypes)
      • Constructor< T > getDeclaredConstructor(类<?>... parameterTypes)
      • Constructor<?>[] getDeclaredConstructors()

      用来 创建对象:

      (1)T newInstance(Object... initargs)

      (2)如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法


      3)获取成员方法对象们:

      • Method[] getMethods()
      • Method getMethod(String name, 类<?>... parameterTypes)
      • Method[] getDeclaredMethods()
      • Method getDeclaredMethod(String name, 类<?>... parameterTypes)

      对于成员方法对象的操作:

      (1)执行方法:Object invoke(Object obj, Object... args参数列表)

      (2)获取方法名称:String getName //获取方法名


      4)获取全类名

      • String getName()

  • 案例:

    • 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
    • 实现:配置文件;反射
    • 步骤:

    1)将需要创建的对象的全类名和需要执行的方法定义在配置文件中

    2)在程序中加载读取配置文件

    3)使用反射技术来加载类文件进内存

    4)创建对象

    5)执行方法

简单实现:

//一、获取class目录下的配置文件
//1.先获得字节码文件对于的类加载器.getClassLoader()
ClassLoader classloader = ReflectTest.class.getClassLoader();
//2.这一步有两个方法
//法一:getResource(String name):获取资源路径
//法二:getResourceAsStream(String name):获取资源对应的字节流
InputStream is = classloader.getResourceAsStream("pro.properties");
pro.load(is);

//二、获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");

//三、加载该类进内存
Class cls = Class.forName(className);

//四、创建对象
Object obj = cls.newInstance();

//五、获取方法对象
Method method = cls.getMethod(methodName);

//六、执行方法
method.invoke(obj);

20. 注解

  • 概念:说明程序的。给计算机看的

  • 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释

  • 概念描述:

    • JDK1.5之后的新特性
    • 说明程序的
    • 使用注解:@注解名称
  • 作用分类:

1)编写文档:通过代码里标识的注解生成文档【生成文档doc文档,指令:java doc】
2)代码分析:通过代码里标识的注解对代码进行分析【使用反射】
3)编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

  • 需要掌握:

1)JDK中预定的一些注解

2)自定义注解

3)在程序中使用(解析)注解


  • JDK中预定义的一些注解
    • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
    • @Deprecated:该注解标注的内容,表示已过时
    • @SuppressWarnings:压制警告
      • 一般传递参数all,例如:@SuppressWarnings("all")

  • 自定义注解

    • 格式:
      元注解
      public @interface 注解名称{
      属性列表;
      }

    • 本质:注解本质上就是一个接口,该接口默认继承Annotation接口

    • 例:public interface MyAnno extends java.lang.annotation.Annotation {}

    • 属性:接口中的抽象方法

      • 要求:

        1)属性的返回值类型有下列取值

        • 基本数据类型
        • String
        • 枚举
        • 注解
        • 以上类型的数组

        2)定义了属性,在使用时需要给属性赋值

        • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值

        • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。

        • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

    • 元注解:用于描述注解的注解

      • @Target:描述注解能够作用的位置
        • ElementType取值:
          • TYPE:可以作用于类上
          • METHOD:可以作用于方法上
          • FIELD:可以作用于成员变量上
      • @Retention:描述注解被保留的阶段
        • @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
      • @Documented:描述注解是否被抽取到api文档中
      • @Inherited:描述注解是否被子类继承

  • 在程序使用(解析)注解:获取注解中定义的属性值

    1)获取注解定义的位置的对象 (Class,Method,Field)

    2)获取指定的注解

    • getAnnotation(Class):其实就是在内存中生成了一个该注解接口的子类实现对象

    3)调用注解中的抽象方法获取配置的属性值

代码示例:

//********************Pro注解的定义****************************
package cn.itcast.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 描述需要执行的类名,和方法名
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
//***********************************************************


//********************Pro注解在程序中的使用(解析)***************
package cn.itcast.annotation;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
 * 框架类
 */
@Pro(className = "cn.itcast.annotation.Demo1",methodName = "show")
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        /*
            前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
         */
        
        //1.解析注解
        //1.1获取该类的字节码文件对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        
        //2.获取上边的注解对象
        //其实就是在内存中生成了一个该注解接口的子类实现对象
        /*
            public class ProImpl implements Pro{
                public String className(){
                    return "cn.itcast.annotation.Demo1";
                }
                public String methodName(){
                    return "show";
                }
            }
            */
        Pro an = reflectTestClass.getAnnotation(Pro.class);
        
        //3.调用注解对象中定义的抽象方法,获取返回值
        String className = an.className();
        String methodName = an.methodName();
        System.out.println(className);
        System.out.println(methodName);

        //4.加载该类进内存
        Class cls = Class.forName(className);
        
        //5.创建对象
        Object obj = cls.newInstance();
        
        //6.获取方法对象
        Method method = cls.getMethod(methodName);
        
        //7.执行方法
        method.invoke(obj);
    }
}

21. 数组、String、集合 计算长度

数组:.length

String:length()

集合:size()

22. java.util.Scanner 常用方法

  • boolean hasNext()
  • String next():读一串字符串,遇到空格啥的就不读了
  • boolean hasNextBoolean()
  • boolean nextBoolean()
  • boolean hasNextByte()
  • byte nextByte()
  • boolean hasNextDouble()
  • double nextDouble()
  • boolean hasNextFloat()
  • float nextFloat()
  • boolean hasNextInt()
  • int nextInt()
  • boolean hasNextLine()
  • String nextLine():读一行数据为字符串
  • boolean hasNextLong()
  • long nextLong()
  • boolean hasNextShort()
  • short nextShort()

23. java.lang.String 常用方法

  • char charAt(int index):返回指定索引的char值
  • int compareTo(String anotherString):按字典顺序比较两个字符串;小于,返回小于0的数;等于,返回0;大于,大于0
  • int compareToIgnoreCase(String str):按字典顺序比较两个字符串,不考虑大小写
  • String concat(String str):将指定字符串连接到此字符串的结尾
  • boolean contains(CharSequence s):字符串是不是包含char序列
  • boolean endsWith(String str):字符串是不是以str结尾
  • boolean equals(String str):比较字符串内容相不相等
  • boolean equalsIgnoreCase(String str):比较内容,不考虑大小写
  • byte[] getBytes():字符串转换成Byte序列
  • int hashCode():返回字符串的哈希码
  • int indexOf(... ...):返回第一次出现这个字符或字符串的索引
  • int lastIndexOf(... ...):返回最后一次出现这个字符或字符串的索引
  • int length():返回字符串的长度
  • boolean matches(String regex):判断字符串是不是匹配指定的正则表达式
  • String replace(... old..., ... new...):返回一个替代后的新的字符串
  • String replaceAll(... ...,... ...)
  • String replaceFirst(... ...,... ...)
  • String[] split(String regex):按照什么来拆分字符串
  • boolean startsWith(String str):是不是以指定字符串为前缀
  • String substring(int begin);String substring(int begin, int end):提取一段为新的字符串
  • char[] toCharArray():字符串转换为新的字符数组
  • String toLowerCase():字符串小写化
  • String toUpperCase():字符串大写化
# Java 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×