javaSE-day05(lambda,正则)

发布时间 2023-03-23 20:05:15作者: 王小子

Lambda表达式

JDK8新增的一种语法形式,叫做Lambda表达式。作用:用于简化匿名内部类代码的书写。
1.Lambda表达式基本使用
Lamdba是有特有的格式的,按照下面的格式来编写Lamdba。

(被重写方法的形参列表) -> {
    被重写方法的方法体代码;
}

需要给说明一下的是,在使用Lambda表达式之前,必须先有一个接口,而且接口中只能有一个抽象方法。(注意:不能是抽象类,只能是接口)

像这样的接口,我们称之为函数式接口,只有基于函数式接口的匿名内部类才能被Lambda表达式简化。

public interface Swimming{
    void swim();
}

有了以上的Swimming接口之后,接下来才能再演示,使用Lambda表达式,简化匿名内部类书写。

public class LambdaTest1 {
    public static void main(String[] args) {
        // 目标:认识Lambda表达式.
        //1.创建一个Swimming接口的匿名内部类对象
		Swimming s = new Swimming(){
             @Override
             public void swim() {
                 System.out.println("学生快乐的游泳~~~~");
             }
         };
         s.swim();
		
        //2.使用Lambda表达式对Swimming接口的匿名内部类进行简化
        Swimming s1 = () -> {
              System.out.println("学生快乐的游泳~~~~");
        };
        s1.swim();
    }
}

好的,我们现在已经知道Lamdba表达式可以简化基于函数式接口的匿名内部类的书写。接下来,我们可以把刚才使用Arrays方法时的代码,使用Lambda表达式简化一下了。

public class LambdaTest2 {
    public static void main(String[] args) {
        // 目标:使用Lambda简化函数式接口。
        double[] prices = {99.8, 128, 100};
		//1.把所有元素*0.8: 先用匿名内部类写法
        Arrays.setAll(prices, new IntToDoubleFunction() {
            @Override
            public double applyAsDouble(int value) {
                // value = 0  1  2
                return prices[value] * 0.8;
            }
        });
		//2.把所有元素*0.8: 改用Lamdba表达式写法
        Arrays.setAll(prices, (int value) -> {
                return prices[value] * 0.8;
        });

        System.out.println(Arrays.toString(prices));
        System.out.println("-----------------------------------------------");
		
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
		//3.对数组中的元素按照年龄升序排列: 先用匿名内部类写法
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
            }
        });
		//4.对数组中的元素按照年龄升序排列: 改用Lambda写法
        Arrays.sort(students, (Student o1, Student o2) -> {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
        });
        System.out.println(Arrays.toString(students));
    }
}

2.Lambda表达式省略规则
Java觉得代码还不够简单,于是还提供了Lamdba表达式的几种简化写法。具体的简化规则如下

1.Lambda的标准格式
	(参数类型1 参数名1, 参数类型2 参数名2)->{
		...方法体的代码...
		return 返回值;
	}

2.在标准格式的基础上()中的参数类型可以直接省略
	(参数名1, 参数名2)->{
		...方法体的代码...
		return 返回值;
	}
	
3.如果{}总的语句只有一条语句,则{}可以省略、return关键字、以及最后的“;”都可以省略
	(参数名1, 参数名2)-> 结果
	
4.如果()里面只有一个参数,则()可以省略
	(参数名)->结果

接下来从匿名内部类开始、到Lambda标准格式、再到Lambda简化格式,一步一步来简化一下。同学们体会一下简化的过程。

public class LambdaTest2 {
    public static void main(String[] args) {
        // 目标:使用Lambda简化函数式接口。
        double[] prices = {99.8, 128, 100};
		//1.对数组中的每一个元素*0.8: 匿名内部类写法
        Arrays.setAll(prices, new IntToDoubleFunction() {
            @Override
            public double applyAsDouble(int value) {
                // value = 0  1  2
                return prices[value] * 0.8;
            }
        });
		//2.需求:对数组中的每一个元素*0.8,使用Lambda表达式标准写法
        Arrays.setAll(prices, (int value) -> {
                return prices[value] * 0.8;
        });
		//3.使用Lambda表达式简化格式1——省略参数类型
        Arrays.setAll(prices, (value) -> {
            return prices[value] * 0.8;
        });
		//4.使用Lambda表达式简化格式2——省略()
        Arrays.setAll(prices, value -> {
            return prices[value] * 0.8;
        });
        //5.使用Lambda表达式简化格式3——省略{}
        Arrays.setAll(prices, value -> prices[value] * 0.8 );

        System.out.println(Arrays.toString(prices));
        
        System.out.println("------------------------------------

        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
		
        //1.使用匿名内部类
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
            }
        });
		//2.使用Lambda表达式表达式——标准格式
        Arrays.sort(students, (Student o1, Student o2) -> {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
        });
		//3.使用Lambda表达式表达式——省略参数类型
        Arrays.sort(students, ( o1,  o2) -> {
            return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
        });
		//4.使用Lambda表达式表达式——省略{}
        Arrays.sort(students, ( o1,  o2) -> Double.compare(o1.getHeight(), o2.getHeight()));


        System.out.println(Arrays.toString(students));
    }
}

方法引用

JDK8的另一个新特性,叫做方法引用。我们知道Lambda是用来简化匿名代码的书写格式的,而方法引用是用来进一步简化Lambda表达式的,它简化的更加过分。
1.静态方法引用
准备好下面的代码

public class Test1 {
    public static void main(String[] args) {
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);

        // 原始写法:对数组中的学生对象,按照年龄升序排序
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge(); // 按照年龄升序排序
            }
        });

        // 使用Lambda简化后的形式
        Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());
    }
}

现在,我想要把Lambda表达式的方法体,用一个静态方法代替

// 使用Lambda简化后的形式
		Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());

准备另外一个类CompareByData类,用于封装Lambda表达式的方法体代码;

public class CompareByData {
    public static int compareByAge(Student o1, Student o2){
        return o1.getAge() - o2.getAge(); // 升序排序的规则
    }
}

现在我们就可以把Lambda表达式的方法体代码,改为下面的样子

Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));

Java为了简化上面Lambda表达式的写法,利用方法引用可以改进为下面的样子。实际上就是用类名调用方法,但是把参数给省略了。这就是静态方法引用

//静态方法引用:类名::方法名
Arrays.sort(students, CompareByData::compareByAge);

2.实例方法引用
还是基于上面的案例,我们现在来学习一下实例方法的引用。现在,我想要把下图中Lambda表达式的方法体,用一个实例方法代替。

// 使用Lambda简化后的形式
		Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());

在CompareByData类中,再添加一个实例方法,用于封装Lambda表达式的方法体

	public class CompareByData {
		public static int compareByAge(Student o1, Student o2){
			return o1.getAge() - o2.getAge(); // 升序排序的规则
		}
		public int compareByAge(Student o1,Student o2){
			return o2.getAge() - o1.getAge();
		}
	}

接下来,我们把Lambda表达式的方法体,改用对象调用方法

CompareByData compare = new CompareByData();
Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2)); // 降序

最后,再将Lambda表达式的方法体,直接改成方法引用写法。实际上就是用类名调用方法,但是省略的参数。这就是实例方法引用

CompareByData compare = new CompareByData();
Arrays.sort(students, compare::compareByAgeDesc); // 降序

3.特定类型的方法引用
这种特定类型的方法引用是没有什么道理的,只是语法的一种约定,遇到这种场景,就可以这样用。

Java约定:
    如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数作为方法的主调,	后面的所有参数都是作为该实例方法的入参时,则就可以使用特定类型的方法引用。
格式:
	类型::方法名

public class Test2 {
    public static void main(String[] args) {
        String[] names = {"boby", "angela", "Andy" ,"dlei", "caocao", "Babo", "jack", "Cici"};
        
        // 要求忽略首字符大小写进行排序。
        Arrays.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 制定比较规则。o1 = "Andy"  o2 = "angela"
                return o1.compareToIgnoreCase(o2);
            }
        });
		
        //lambda表达式写法
        Arrays.sort(names, ( o1,  o2) -> o1.compareToIgnoreCase(o2) );
        //特定类型的方法引用!
        Arrays.sort(names, String::compareToIgnoreCase);

        System.out.println(Arrays.toString(names));
    }
}

4.构造器引用
构造器引用在实际开发中应用的并不多,目前还没有找到构造器的应用场景。所以大家在学习的时候,也只是关注语法就可以了。

现在,我们准备一个JavaBean类,Car类

public class Car {
    private String name;
    private double price;

    public Car() {

    }

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

因为方法引用是基于Lamdba表达式简化的,所以也要按照Lamdba表达式的使用前提来用,需要一个函数式接口,接口中代码的返回值类型是Car类型

interface CreateCar{
    Car create(String name, double price);
}

最后,再准备一个测试类,在测试类中创建CreateCar接口的实现类对象,先用匿名内部类创建、再用Lambda表达式创建,最后改用方法引用创建。同学们只关注格式就可以,不要去想为什么(语法就是这么设计的)。

public class Test3 {
    public static void main(String[] args) {
        // 1、创建这个接口的匿名内部类对象。
        CreateCar cc1 = new CreateCar(){
            @Override
            public Car create(String name, double price) {
                return new Car(name, price);
            }
        };
		//2、使用匿名内部类改进
        CreateCar cc2 = (name,  price) -> new Car(name, price);

        //3、使用方法引用改进:构造器引用
        CreateCar cc3 = Car::new;
        
        //注意:以上是创建CreateCar接口实现类对象的几种形式而已,语法一步一步简化。
        
        //4、对象调用方法
        Car car = cc3.create("奔驰", 49.9);
        System.out.println(car);
    }
}

正则表达式

正则表达式其实是由一些特殊的符号组成的,它代表的是某种规则。

正则表达式的作用1:用来校验字符串数据是否合法

正则表达式的作用2:可以从一段文本中查找满足要求的内容
image

现在,我们就以QQ号码为例,来体验一下正则表达式的用法。注意:现在仅仅只是体验而已,我们还没有讲正则表达式的具体写法。

  • 不使用正则表达式,校验QQ号码代码是这样的

      public static boolean checkQQ(String qq){
      		// 1、判断qq号码是否为null
      		if(qq == null || qq.startsWith("0") || qq.length() < 6 || qq.length() > 20){
      			return false;
      		}
    
          // 2、qq至少是不是null,不是以0开头的,满足6-20之间的长度。
          // 判断qq号码中是否都是数字。
          // qq = 2514ghd234
          for (int i = 0; i < qq.length(); i++) {
              // 根据索引提取当前位置处的字符。
              char ch = qq.charAt(i);
              // 判断ch记住的字符,如果不是数字,qq号码不合法。
              if(ch < '0' || ch > '9'){
                  return false;
              }
          }
          // 3、说明qq号码肯定是合法
          return true;
      }
    
  • 用正则表达式代码是这样的

      public static boolean checkQQ1(String qq){
      	return qq != null && qq.matches("[1-9]\\d{5,19}");
      }
    

我们发现,使用正则表达式,大大简化的了代码的写法。这个代码现在不用写,体验到正则表达式的优势就可以了。

书写规则
前面我们已经体验到了正则表达式,可以简化校验数据的代码书写。这里需要用到一个方法叫matches(String regex)。这个方法时属于String类的方法。

这个方法是用来匹配一个字符串是否匹配正则表达式的规则,参数需要调用者传递一个正则表达式。但是正则表达式不能乱写,是有特定的规则的。
image
我们将这些规则,在代码中演示一下

/**
 * 目标:掌握正则表达式的书写规则
 */
public class RegexTest2 {
    public static void main(String[] args) {
        // 1、字符类(只能匹配单个字符)
        System.out.println("a".matches("[abc]"));    // [abc]只能匹配a、b、c
        System.out.println("e".matches("[abcd]")); // false

        System.out.println("d".matches("[^abc]"));   // [^abc] 不能是abc
        System.out.println("a".matches("[^abc]"));  // false

        System.out.println("b".matches("[a-zA-Z]")); // [a-zA-Z] 只能是a-z A-Z的字符
        System.out.println("2".matches("[a-zA-Z]")); // false

        System.out.println("k".matches("[a-z&&[^bc]]")); // : a到z,除了b和c
        System.out.println("b".matches("[a-z&&[^bc]]")); // false

        System.out.println("ab".matches("[a-zA-Z0-9]")); // false 注意:以上带 [内容] 的规则都只能用于匹配单个字符

        // 2、预定义字符(只能匹配单个字符)  .  \d  \D   \s  \S  \w  \W
        System.out.println("徐".matches(".")); // .可以匹配任意字符
        System.out.println("徐徐".matches(".")); // false

        // \转义
        System.out.println("\"");
        // \n \t
        System.out.println("3".matches("\\d"));  // \d: 0-9
        System.out.println("a".matches("\\d"));  //false

        System.out.println(" ".matches("\\s"));   // \s: 代表一个空白字符
        System.out.println("a".matches("\s")); // false

        System.out.println("a".matches("\\S"));  // \S: 代表一个非空白字符
        System.out.println(" ".matches("\\S")); // false

        System.out.println("a".matches("\\w"));  // \w: [a-zA-Z_0-9]
        System.out.println("_".matches("\\w")); // true
        System.out.println("徐".matches("\\w")); // false

        System.out.println("徐".matches("\\W"));  // [^\w]不能是a-zA-Z_0-9
        System.out.println("a".matches("\\W"));  // false

        System.out.println("23232".matches("\\d")); // false 注意:以上预定义字符都只能匹配单个字符。

        // 3、数量词: ?   *   +   {n}   {n, }  {n, m}
        System.out.println("a".matches("\\w?"));   // ? 代表0次或1次
        System.out.println("".matches("\\w?"));    // true
        System.out.println("abc".matches("\\w?")); // false

        System.out.println("abc12".matches("\\w*"));   // * 代表0次或多次
        System.out.println("".matches("\\w*"));        // true
        System.out.println("abc12张".matches("\\w*")); // false

        System.out.println("abc12".matches("\\w+"));   // + 代表1次或多次
        System.out.println("".matches("\\w+"));       // false
        System.out.println("abc12张".matches("\\w+")); // false

        System.out.println("a3c".matches("\\w{3}"));   // {3} 代表要正好是n次
        System.out.println("abcd".matches("\\w{3}"));  // false
        System.out.println("abcd".matches("\\w{3,}"));     // {3,} 代表是>=3次
        System.out.println("ab".matches("\\w{3,}"));     // false
        System.out.println("abcde徐".matches("\\w{3,}"));     // false
        System.out.println("abc232d".matches("\\w{3,9}"));     // {3, 9} 代表是  大于等于3次,小于等于9次

        // 4、其他几个常用的符号:(?i)忽略大小写 、 或:| 、  分组:()
        System.out.println("abc".matches("(?i)abc")); // true
        System.out.println("ABC".matches("(?i)abc")); // true
        System.out.println("aBc".matches("a((?i)b)c")); // true
        System.out.println("ABc".matches("a((?i)b)c")); // false

        // 需求1:要求要么是3个小写字母,要么是3个数字。
        System.out.println("abc".matches("[a-z]{3}|\\d{3}")); // true
        System.out.println("ABC".matches("[a-z]{3}|\\d{3}")); // false
        System.out.println("123".matches("[a-z]{3}|\\d{3}")); // true
        System.out.println("A12".matches("[a-z]{3}|\\d{3}")); // false

        // 需求2:必须是”我爱“开头,中间可以是至少一个”编程“,最后至少是1个”666“
        System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+"));
        System.out.println("我爱编程编程66666".matches("我爱(编程)+(666)+"));
    }
}

应用
学习完正则表达式的规则之后,接下来我们再利用正则表达式,去校验几个实际案例。

  • 正则表达式校验手机号码

      /**
       * 目标:校验用户输入的电话、邮箱、时间是否合法。
       */
      public class RegexTest3 {
      	public static void main(String[] args) {
      		checkPhone();
      	}
    
      public static void checkPhone(){
          while (true) {
              System.out.println("请您输入您的电话号码(手机|座机): ");
              Scanner sc = new Scanner(System.in);
              String phone = sc.nextLine();
              // 18676769999  010-3424242424 0104644535
              if(phone.matches("(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})")){
                  System.out.println("您输入的号码格式正确~~~");
                  break;
              }else {
                  System.out.println("您输入的号码格式不正确~~~");
              }
          }
      }
    

    }

  • 使用正则表达式校验邮箱是否正确

      public class RegexTest3 {
      	public static void main(String[] args) {
      		checkEmail();
      	}
    
      public static void checkEmail(){
          while (true) {
              System.out.println("请您输入您的邮箱: ");
              Scanner sc = new Scanner(System.in);
              String email = sc.nextLine();
              /**
               * dlei0009@163.com
               * 25143242@qq.com
               * itheima@itcast.com.cn
               */
              if(email.matches("\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2}")){
                  System.out.println("您输入的邮箱格式正确~~~");
                  break;
              }else {
                  System.out.println("您输入的邮箱格式不正确~~~");
              }
          }
      }
      }
    

信息爬取
我们学习了正则表达式的作用之一,用来校验数据格式的正确性。接下来我们学习正则表达式的第二个作用:在一段文本中查找满足要求的内容

/**
 * 目标:掌握使用正则表达式查找内容。
 */
public class RegexTest4 {
    public static void main(String[] args) {
        method1();
    }

    // 需求1:从以下内容中爬取出,手机,邮箱,座机、400电话等信息。
    public static void method1(){
        String data = " 来学习Java,\n" +
                "        电话:1866668888,18699997777\n" +
                "        或者联系邮箱:boniu@itcast.cn,\n" +
                "        座机电话:01036517895,010-98951256\n" +
                "        邮箱:bozai@itcast.cn,\n" +
                "        邮箱:dlei0009@163.com,\n" +
                "        热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
        // 1、定义爬取规则
        String regex = "(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})|(\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2})"
                + "|(400-?\\d{3,7}-?\\d{3,7})";
        // 2、把正则表达式封装成一个Pattern对象
        Pattern pattern = Pattern.compile(regex);
        // 3、通过pattern对象去获取查找内容的匹配器对象。
        Matcher matcher = pattern.matcher(data);
        // 4、定义一个循环开始爬取信息
        while (matcher.find()){
            String rs = matcher.group(); // 获取到了找到的内容了。
            System.out.println(rs);
        }
    }
}

正则表达式搜索、替换

接下来,我们学习一下正则表达式的另外两个功能,替换、分割的功能。需要注意的是这几个功能需要用到Stirng类中的方法。这两个方法其实我们之前学过,只是当时没有学正则表达式而已。

/**
 * 目标:掌握使用正则表达式做搜索替换,内容分割。
 */
public class RegexTest5 {
    public static void main(String[] args) {
        // 1、public String replaceAll(String regex , String newStr):按照正则表达式匹配的内容进行替换
        // 需求1:请把下面字符串中的不是汉字的部分替换为 “-”
        String s1 = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
        System.out.println(s1.replaceAll("\\w+", "-"));
        
        // 需求2(拓展):某语音系统,收到一个口吃的人说的“我我我喜欢编编编编编编编编编编编编程程程!”,需要优化成“我喜欢编程!”。
        String s2 = "我我我喜欢编编编编编编编编编编编编程程程";
        System.out.println(s2.replaceAll("(.)\\1+", "$1"));

        // 2、public String[] split(String regex):按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
        // 需求1:请把下面字符串中的人名取出来,使用切割来做
        String s3 = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
        String[] names = s3.split("\\w+");
        System.out.println(Arrays.toString(names));
    }
}