1. 什么是API?
- API (Application Programming interface) 应用程序编程接口。
- 通俗来讲,就是Java已经帮我们写好了一些方法,我们直接拿过来用即可。
iodraw:https://www.iodraw.com/
API在线中文文档:https://www.matools.com/api/java8
当然也可以IntelliJ 在IDEA中查看Java源码。
2. Object、Objects类
2.1 Object类
-
Object类位于java.lang包,在Java中所有的类都是直接或者间接继承该类。
-
Object类的方法是一切子类都可以使用的。
Object类常用的方法:
方法名 | 说明 |
---|---|
public String toString() | 默认是返回当前对象在堆内存中的地址信息:类的全限定名@内存地址 |
public boolean equals(Object obj) | 默认是比较两个对象的地址是否相同(==),相同返回true,不同返回false |
public final Class getClass() | 返回Class类型的对象 |
protected Object clone() | 创建并返回此对象的副本 |
public int hashCode() | 返回该对象的哈希码值。默认情况下,该方法会根据对象的地址来计算。 |
不同对象的 hashCode的值一般是不相同。但是,同一个对象的hashCode值肯定相同。
2.2 Objects类
- Objects类与Object还是继承关系,它位于java.util包,Objects类是JDK1.7之后开始有的。
- 相比之下Objects中的equals()方法更加安全(多了个非空判断)。
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
Objects类的常用方法:
方法名 | 说明 |
---|---|
public boolean equals(Object a,Object b) | 比较两个对象的地址是否相同,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较 |
public static boolean isNull(Object obj) | 判断变量是否为null |
3. Math类
-
Math类提供了一些数字运算的方法(如:指数,对数,平方根、三角函数等等),它是一个数学工具类不需要创建对象(因此构造器私有化)。
-
使用:Math中许多成员都被static修饰了,直接用类名.xxx调用即可。
4. System类
System的功能是通用的,都是直接用类名调用即可,所以System不能被实例化。
System类常用方法:
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的Java虚拟机,非零表示异常终止 |
public static native long currentTimeMillis() | 返回当前系统时间的毫秒数(时间戳) |
public static void arraycopy(源数组,起始索引,目标数组,起始索引,拷贝个数) | 数组拷贝 |
5. BigDecimal类
用于解决浮点型运算精度丢失问题。
使用方式: 创建BigDecimal对象封装浮点类型数据 (通过valueOf方法创建)
public static BigDecimal valueOf(double val)
BigDecimal类常用API:
方法名 | 说明 |
---|---|
public BigDecimal add(BigDecimal b) | 加法 |
public BigDecimal subract(BigDecimal b) | 减法 |
public BigDecimal multiply(BigDecimal b) | 乘法 |
public BigDecimal divide(BigDecimal b) | 除法 |
6. 日期与时间
6.1 Date
-
Date类的对象可以获取系统当前的日期时间。
-
它在java.util包下。
1、Date类的构造器:
// 创建一个Date对象,代表的是系统当前的日期时间(精确到毫秒)
public Date()
2、Date类常用方法:
// 获取时间对象的毫秒值
public long getTime()
3、时间毫秒值->日期对象:
// 有参构造器
public Date(long time) // 把时间毫秒值转换成Date日期对象
// 设置日期对象的时间,参数为时间毫秒值
public void setTime(long time)
示例:计算当前时间往后走1小时121秒之后的时间是多少?
public class DateTest2 {
public static void main(String[] args) {
// 获取当前系统时间的毫秒值
long time = System.currentTimeMillis();
System.out.println("当前时间为:");
System.out.println(new Date(time));
time += (60 * 60 + 121) * 1000;
System.out.println("当前时间往后走1小时121秒之后的时间是:");
System.out.println(new Date(time));
}
}
6.2 SimpleDateFormat
- SimpleDateFormat可以把Date对象或毫秒值格式化成我们喜欢的时间形式。
- 也可以把字符串的时间形式解析成日期对象。
格式化:
Date对象 -> 2022年6月30日 18:40
时间毫秒值 -> 2022年6月30日 18:40
解析: 2022年6月30日 18:40 -> Date对象
1、SimpleDateFormat的构造器:
构造器 | 说明 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat对象,使用默认格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat对象,使用指定的格式 |
2、SimpleDateFormat的格式化方法:
构造器 | 说明 |
---|---|
public final String format(Date date) | 将日期对象格式化成日期/时间字符串 |
public final String format(Object time) | 将时间毫秒值格式化成日期/时间字符串 |
- 被final修饰的方法不能被重写。
格式化的时间格式:
年: y
月: M
日: d
时: H
分: m
秒: s
完整格式:yyyy-MM-dd HH:mm:ss (2022-6-30 19:04:30)
3、SimpleDateFormat解析字符串时间并返回日期对象:
解析方法 | 说明 |
---|---|
public Date parse(String source) | 从给定字符串的开始解析文本来生成日期 |
示例:计算出2022年06月30日 19点06分55秒,往后走2天14小时49分06秒后的时间是多少。
public class SimpleDateFormatTest2 {
public static void main(String[] args) throws ParseException {
// 1、定义字符串时间
String dateStr = "2022年06月30日 19点06分55秒";
// 2、将字符串时间解析成日期对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH点mm分ss秒");
Date date = sdf.parse(dateStr);
// 3、将时间毫秒值往后走2天14小时49分06秒
long time = date.getTime() + (24 * 60 * 60 * 2L + 14 * 60 * 60 + 49 * 60 + 6) * 1000;
// 4、格式化结果
dateStr = sdf.format(time);
System.out.println(dateStr); // 2022年07月03日 09点56分01秒
}
}
6.3 Calendar
- Calendar代表系统此刻日期对应的日历对象。
- Calendar是一个抽象类,不能直接创建对象。
// 通过方法获取实例(多态)
public static Calendar getlnstance()
常用方法:
方法名 | 说明 |
---|---|
public int get(int field) | 获取日期中的某个字段信息 |
public void set(int field, int value) | 修改日历的某个字段 |
public void add(int field, int amount) | 为某个字段添加/减少指定的值 |
public final Date getTime() | 拿到此刻日期对象 |
public long getTimeInMillis() | 拿到此刻时间毫秒值 |
注意:Calendar是可变日期对象,一旦修改后会其对象本身表示的时间会发生变化。
示例:Calendar的使用
// 获取的操作用的多
public class CalendarTest {
public static void main(String[] args) {
// 1、拿到系统此刻的日历对象
Calendar cal = Calendar.getInstance();
//System.out.println(cal);
/*
字段对应的数据:
java.util.GregorianCalendar[
time=1656591237954,
areFieldsSet=true,
areAllFieldsSet=true,
lenient=true,
zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null],
firstDayOfWeek=1,
minimalDaysInFirstWeek=1,
ERA=1,
YEAR=2022,
MONTH=5,
WEEK_OF_YEAR=27,
WEEK_OF_MONTH=5,
DAY_OF_MONTH=30,
DAY_OF_YEAR=181,
DAY_OF_WEEK=5,
DAY_OF_WEEK_IN_MONTH=5,
AM_PM=1,HOUR=8,
HOUR_OF_DAY=20,
MINUTE=13,
SECOND=57,
MILLISECOND=954,
ZONE_OFFSET=28800000,
DST_OFFSET=0]
*/
// 2、获取日历的信息
int year = cal.get(Calendar.YEAR);
System.out.println("year = " + year);
int month = cal.get(Calendar.MONTH + 1);
System.out.println("month = " + month);// 它从0开始
int days = cal.get(Calendar.DAY_OF_YEAR);
System.out.println("今年已将过去" + days+"天"); //今年已将过去181天
// 3、public void set(int field, int value):修改日历某个字段信息,通常不修改
// 修改年
// cal.set(Calendar.YEAR,2023);
// System.out.println("修改之后的年份为:"+cal.get(Calendar.YEAR));
// 4、public void add(int field, int amount):为某个字段添加/减少指定的值
// 64天后是什么时间
cal.add(Calendar.DAY_OF_YEAR,64);
// 5、public final Date getTime():拿到此刻日期对象,不常用
Date date = cal.getTime();
System.out.println("date="+date);//date=Fri Sep 02 20:29:53 CST 2022
// 6、public long getTimeInMillis():拿到此刻时间毫秒值,不常用
System.out.println(cal.getTimeInMillis()); //1662121793001
}
}
7. JDK8 新增的日期类
7.1 LocalDate、LocalTime、LocalDateTime
- LocalDate、LocalTime、LocalDateTime:他们分别表示日期,时间,日期时间对象,他们的类的实例是不可变的对象。
- 他们三者构建对象和API都是通用的。
7.2 Instant(时间戳)
7.3 DateTimeFormatter
7.4 Duration、Period
-
Duration: 用于计算两个 “时间” 间隔。
-
Period: 用于计算两个 “日期” 间隔。
7.5 ChronoUnit
8. 包装类
包装类是Java提供的一组类,专门用来创建8种基本数据类型对应的对象,一共8个包装类,存放在 java.lang包。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
8.1 为什么要有包装类?
- Java为了万物皆对象,为8种基本类型提供了对应的包装类。
- 集合和泛型不支持基本数据类型,只能支持包装类型。
包装类体系结构如下:
示例:
public class IntegerDemo {
public static void main(String[] args) {
// 定义一个字符串
String str = "123";
// 将字符串转换成整型
int num = Integer.parseInt(str);
System.out.println(num++); // 123
}
}
8.2 自动装箱和拆箱
- 装箱:将基本数据类型转为对应的包装类对象,利用各个包装类的构造方法完成。
- 拆箱:将包装类对象转为对应的基本数据类型,利用Number类的xxxValue()方法完成(xxx表示基本数据类型的名称)。
- JDK1.5之前,使用手动方式进行装箱和拆箱的操作。
- JDK1.5之后,使用**自动装箱(Autoboxing)和自动拆箱(Auto Unboxing)**的操作。(所以我们使用的便利是拿编译器的辛苦换来的🤭)
示例:
public class TestBoxingAndUnboxing {
public static void main(String[] args) {
// 1、基本数据类型变为包装类,装箱
// 通过传入基本类型数据,然后使用new关键字调用Integer类的构造方法,将其变为Integer类的对象intObj,整个过程为装箱操作。
Integer intObj = new Integer(10);
// 2、包装类变为基本类型,拆箱
// 将包装类Integer类的对象intObj,还原为基本类型,并赋值给整型变量temp,整个过程为拆箱操作。
int temp = intObj.intValue();
System.out.println("整型变量temp的值为:" + temp);
System.out.println("---------------分割线-----------------");
int temp2 = 345;
// 3、自动装箱
//在编译阶段,编译器会自动将 intObj = temp2; 这条语句扩转为:intObj = intObj.valueOf(temp2);
intObj = temp2;
// 4、自动拆箱
//在编译阶段,编译器会自动将 int temp3 = intObj; 这条语句扩转为: int temp3 = intObj.intValue();
int temp3 = intObj;
temp3++;
System.out.println("整型变量temp3的值为:" + temp3);
// 自动装箱
Boolean boo = true;
// 自动拆箱
boolean flag = boo;
System.out.println(flag && false);
}
}
8.3 基本类型与字符串的转换
使用包装类的特点是将字符串变为指定的基本类型数据。
- 以Integer为例: public static int parseInt(String s);
- 以Double为例: public static int parseDouble(String s);
- 以Boolean为例: public static int parseBoolean(String s);
但是以上的操作方法形式对于字符类型(character)是不存在的。因为String类有一个charAt()方法,可以取得指定索引的字符。
示例:
public class Test3 {
public static void main(String[] args) {
// 定义一个字符串
String str = "3.14";
// 将字符串转为double
double d = Double.parseDouble(str);
System.out.println("d:" + d);
//重新给字符串赋值
str = "true";
boolean flag = Boolean.parseBoolean(str);
if (flag){
System.out.println("条件满足!");
}else{
System.out.println("条件不满足!");
}
}
}
运行结果如下:
8.4 将基本类型变为字符串
-
使用String类中的valueOf()方法。
-
或者拼接字符串,str+“”。
示例:
public class Test3 {
public static void main(String[] args) {
int intValue = 100;
// 将整型变量转换成字符串型
String str = String.valueOf(intValue);
System.out.println(str);
double e = 2.718;
// 将double变量转换成字符串型
str = String.valueOf(e);
System.out.println(str);
}
}
运行结果如下:
8.5 Integer数值比较
public class Test {
public static void main(String[] args) {
// 自动装箱,相当于 Integer.valueOf();
Integer a = 200;
Integer b = 200;
Integer c = 100;
Integer d = 100;
int e = 200;
System.out.println(a == b); // false
System.out.println(c == d); // true
// 先自动拆箱相当于Integer.intValue();,然后再判断是否相等
System.out.println(b == e); // true
}
}
如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池
中的 Integer 对象,所以 c = = d 的结果是 true,而 a = = b 的结果是 false。
9. 正则表达式
正则表达式可以用一些规定的字符来制定规则,并用来校验数据格式的合法性。例如:表单验证等等
需求:校验一个QQ号码是否正确,6位或20位号之内必须全部是数字
public class RegexDemo1 {
public static void main(String[] args) {
// System.out.println(check1("1234567889"));
// System.out.println(check1("123456a7889"));
// 使用正则表达式校验
System.out.println(check2("1234567889"));
}
/**
* 常规写法
*
* @param qq
* @return
*/
public static boolean check1(String qq) {
// 1、判断qq长度是否满足要求
if (qq == null || qq.length() < 6 || qq.length() > 20) {
return false;
}
//2、使用循环遍历每个字符,然后判断qq中是否全部是数字
for (int i = 0; i < qq.length(); i++) {
char ch = qq.charAt(i);
// 非数字判断
if (ch < '0' || ch > '9') {
return false;
}
}
return true;
}
/**
* 使用正则表达式校验
*
* @param qq
* @return
*/
public static boolean check2(String qq) {
/*
\d 表示全部是数字
\\ 转义字符 -> \
{6,20} 表示[6,20]之间的数
*/
return qq != null && qq.matches("\\d{6,20}");
}
}
9.1 正则表达式的匹配规则
java.util.regex.Pattern匹配规则类
1、字符类(默认匹配一个字符)
[abc] 只能是a、b或者c
[^abc] 除了a、b、c之外的任何字符
[a-zA-Z] a到z或A到Z 之间的字符
[a-d[m-p]] a到d或m到p:[a-dm-p](并集)
[a-z&&[def]] d、e或f(交集)
[a-z&&[^bc]] a到z,除了b和c:[ad-z](减法)
[a-z&&[^m-p]] a到z,而非m到 p:[a-lq-z](减法)
2、预定义的字符类(默认匹配一个字符)
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 一个数字:[0-9]
\D 非数字: [^0-9]
\s 一个空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符(英文、数字、下划线):[a-zA-Z_0-9]
\W 一个非单词字符:[^\w]
3、贪婪的量词(配合匹配多个字符)
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,正好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次
4、String类的matches()方法可以与正则表达式进行匹配。
// 判断是否匹配正则表达式,匹配返回true,不匹配返回false。
public boolean matches(String regex)
示例:
System.out.print1n("a".matches("[abc]")); // true
System.out.println("z".matches("[abc]")); // false
System.out.print1n("ab".matches( "[abc]")); // falre,只能匹配一个
System.out.print1n("ab".matches("[abc ]+")); // true
9.2 正则表达式的常见案例
1、请编写程序模拟用户输入手机号码、验证格式正确,并给出提示,直到格式输入正确为止。
public class RegexPhone {
public static void main(String[] args) {
check();
}
public static void check() {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你注册的手机号码:");
String phone = sc.next();
// 判断手机号码格式是否正确,1开头,第二位3-9,之后九位没有要求
if(phone.matches("1[3-9]\\d{9}")){
System.out.println("手机号码格式正确,注册完成~");
break;
}else {
System.out.println("格式有误!");
}
}
}
}
2、请编写程序模拟用户输入邮箱号码、验证格式正确,并给出提示,直到格式输入正确为止。
public class RegexEmail {
public static void main(String[] args) {
check();
}
public static void check() {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你注册的邮箱:");
String email = sc.next();
/*
1433123335@qq.com
\\w{1,30} 用户名
@[a-zA-Z0-9]{2,20} @xxx
\\. -> .
([a-zA-Z]{2,20}){1,2} 有可能一级域名或者两级域名
*/
if(email.matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z]{2,20}){1,2}")){
System.out.println("邮箱格式正确,注册完成~");
break;
}else {
System.out.println("格式有误!");
}
}
}
}
3、请编写程序模拟用户输入电话号码、验证格式正确,并给出提示,直到格式输入正确为止。
public class RegexPhone {
public static void main(String[] args) {
check();
}
public static void check() {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你的电话号码:");
String phone = sc.next();
if(phone.matches("0\\d{2,6}-?\\d{5,20}")){
System.out.println("格式正确,注册完成~");
break;
}else {
System.out.println("格式有误!");
}
}
}
}
9.3 正则表达式在方法中的应用
正则表达式在字符串方法中的使用:
方法名 | 说明 |
---|---|
public String replaceAll(String regex,String newStr) | 按照正则表达式匹配的内容进行替换 |
public String sptit(String regex) | 按照正则表达式匹配的内容进行分割字符串,并返回一个字符串数组。 |
9.4 正则表达式爬取信息
/**
* 需求:从下面信息解析电话号和邮箱
*
* @author 白豆五
* @version 2022/7/1 14:25
* @since JDK8
*/
public class RegexContent {
public static void main(String[] args) {
// 1、编写字符串数据
String rs = "来黑马程序学习Java,电话020-43422424,或者联系邮箱" +
"itcast@itcast.cn,电话18762832633,0203232323" +
"邮箱bozai@itcast.cn,400-100-3233 , 4001003232";
// 2、定义爬取规则
/*
邮箱: \w{1,}@\w{2,10}(\.\w{2,10}){1,2}
手机号: 1[3-9]\d{9}
电话号: 1[3-9]\d{9} , 400-?\d{3,8}-?\d{3,8}
*/
String regex = "(\\w{1,}@\\w{2,10}(\\.\\w{2,10}){1,2})|" +
"(1[3-9]\\d{9})|(0\\d{2,5}-?\\d{5,15})|(400-?\\d{3,8}-?\\d{3,8})";
// 3、编译正则表达式后返回一个匹配规则对象
Pattern pattern = Pattern.compile(regex);
// 4、获取匹配器对象
Matcher matcher = pattern.matcher(rs);
// 5、通过匹配器去内容中爬取信息
while (matcher.find()){
System.out.println(matcher.group());
}
}
}
10. Arrays类
java.util. Arrays数组操作工具类,专门用于操作数组元素。
Arrays类常用API:
方法名 | 说明 |
---|---|
public static String toString(数据类型[] a) | 返回数组中的内容 |
public static void sort(数据类型[] a) | 对数据进行排序(默认升序) |
public static void sort(数据类型[] a, Comparator<? super T> c) | 使用比较器对熊自定义排序 |
public static int binarySearch(int[]a, int key) | 二分搜索数组中的数据,存在返回索引,不存在返回-1 |
拷贝数组:System.arraycopy()比Arrays.copyOf()更高效。
11. 常见算法
11.1 选择排序
思想:每轮选择当前的位置,开始找出后面较小值与该位置交换。
选择排序的关键步骤:
- 确定总共需要选择几轮:数组的长度-1。
- 控制每轮以当前位置为基础,与后面元素进行比较。
public class SelectionSort {
public static void main(String[] args) {
// 1、定义数组
int[] arr = {5, 1, 3, 2};
// 2、定义一个循环,控制选择几轮 arr.length - 1
for (int i = 0; i < arr.length - 1; i++) {
// 定义内部循环,控制内部选择几次
for (int j = i + 1; j < arr.length; j++) {
// 当前位 arr[i]
// 如果后面的数据比当前位的数据还小,则交换
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
11.2 二分查找
-
基本查找:从前往后逐个比较元素,效率低。
-
二分查也叫对半查找,它的性能更好,二分查找的前提是数组必须是排好序的。
-
二分查询相当于每次去掉一半的查找范围。
实现步骤:
1、定义变量记录左边和右边的位置。
2、使用while循环控制查找(条件左边位置<=右边位置)。
3、循环内部获取中间元素索引。
4、判断当前要找的元素如果大于中间元素,左边位置=中间索引+1。
5、判断当前要找的元素如果小于中间元素,右边位置=中间索引-1。
6、判断当前要找的元素如果等于中间元素,返回当前中间元素索引。
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println(binarySearch(arr,30));
}
/**
* 二分查找算法的实现
*
* @param arr 排序的数组
* @param data 要找的数据
* @return 索引,如果元素不存在返回-1
*/
public static int binarySearch(int[] arr, int data) {
// 1、定义左边位置和右边位置
int left = 0;
int right = arr.length - 1;
// 2、循环折半查询
while (left <= right) {
// 取中间索引
int middleIndex = (left + right) / 2;
// 3、判断data是否大于中间元素
if (data > arr[middleIndex]) {
//往右边找
left = middleIndex + 1;
}else if(data<arr[middleIndex]){
//往左边找
right=middleIndex-1;
}else {
return middleIndex;
}
}
return -1;
}
}
12. Lambda表达式
Java8是Java语言自JDK1.5以后的一个重大的里程碑版本,因为它增加了很多新特性,这些新特性会改变编程的风格和解决问题的方式。
例如:日期时间API、Lambda表达式、Stream API(操作集合)、方法引用、CompletableFuture(异步编程)等。
Lambda表达式的作用:简化匿名内部类的写法。
12.1 函数式编程思想
之前我们写的代码都是面向对象的编程思想,该思想强调的是通过对象来做事情。例如 我们想要线程执行任务就必须创建一个实现Runnable接口的实现类对象,但是我们真正要做的事情实际上就是执行run方法中的代码从而来执行线程的任务。
函数式编程思想省略了创建对象的复杂语法,然后通过 lambda表达式 直接传递代码来执行线程任务。而不需要创建 Runnable接口的实现类对象。
函数式编程思想强调的是做什么,而不是以什么方式去做。
面向对象编程和函数式编程两种编程风格的对比:(函数式编程相比oop语法更加整洁)
package com.baidou.java.jdk.feature.java8;
/**
* Lambda表达式的使用
*
* @author baidou 1433021114@qq.com
* @version 2022/6/5 14:14
* @since JDK8
*/
public class LambdaExpressionTest {
public static void main(String[] args) {
new LambdaExpressionTest().testLambdaExpression();
}
/**
* 面向对象编程和函数式编程两种编程风格的对比
*/
public void testLambdaExpression() {
// 面向对象的方式创建和启动线程
new Thread(new CustomRunnable()).start();
// 函数式编程方式创建和启动线程
new Thread(()->{
System.out.println("2.函数式编程方式创建和启动线程");
}).start();
}
}
/**
* 定义一个类实现Runnable接口
*/
class CustomRunnable implements Runnable {
@Override
public void run() {
System.out.println("1.面向对象的方式创建和启动线程");
}
}
12.2 函数式接口
函数式接口表示接口中只有一个抽象方法,而且用注解@FunctionalInterface标记的接口就是函数式接口。
这里需要注意的是,接口中如果有其他的方法(默认方法、静态方法),但是只有一个抽象方法,它也是函数式接口,例如java.util.Comparator接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
自定义函数式接口:
package com.baidou.java.jdk.feature.java8;
/**
* 自定义函数式接口
*
* @author baidou 1433021114@qq.com
* @version 2022/6/5 14:56
* @since JDK8
*/
@FunctionalInterface
public interface CustomFunctionalInterface {
// 实现两个整数相加
int add(int a, int b);
}
12.3 Lambda表达式的使用
要想使用Lambda表达式必需先有函数式接口,Lambda表达式也是函数式编程思想的体现。
Lambda表达式的语法格式
1、Lambda表达式的标准语法格式
()->{}
解释: () 表示重写函数式接口的抽象方法的参数列表
-> 表示lambda表达式的固定组成部分,箭头表示它指向什么
{} 表示重写函数式接口的抽象方法的方法体,也是方法要做的事情
/**
* lambda表达式的完整格式使用
*/
@Test
public void testColloectionsSort() {
List<Integer> list = new ArrayList<>();
list.add(77);
list.add(66);
list.add(11);
list.add(22);
list.add(55);
list.add(1024);
// 使用匿名内部类降序排序
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer value1, Integer value2) {
return value2 - value1;
}
});
System.out.println("使用匿名内部类降序排序list集合的结果是" + list);
// 使用lambda表达式降序排序
Collections.sort(list, (Integer value1, Integer value2) -> {
return value2 - value1;
});
System.out.println("使用lambda表达式降序排序list集合的结果是" + list);
}
2、Lambda表达式的简化语法格式
简化之前:
// 使用lambda表达式降序排序
Collections.sort(list, (Integer value1, Integer value2) -> {
return value2 - value1;
});
- 参数的数据类型可以不写;
- {}中如果只有一条执行语句,
{}
和;
以及return
都可以不写。(三个必须一起省略)
简化之后:
Collections.sort(list, (value1,value2) -> value2 - value1);
Lambda表达式的应用场景
1、变量(使用极少)
package com.baidou.java.jdk.feature.java8;
/**
* 自定义函数式接口
*
* @author baidou 1433021114@qq.com
* @version 2022/6/5 14:56
* @since JDK8
*/
@FunctionalInterface
public interface CustomFunctionalInterface {
// 实现两个整数相加
int add(int a, int b);
}
/**
* lambda表达式的应用场景
*/
@Test
public void testLambdaExpressionUsage() {
// 将lambda表达式赋值给一个变量
CustomFunctionalInterface c = (int a, int b) -> {
return a + b;
};
int result = c.add(12, 34);
System.out.println("result = " + result);
}
2、方法的参数
Collections.sort(list, (value1,value2) -> value2 - value1);
3、方法的返回值
/**
* lambda表达式可以作为方法的返回值
*/
public CustomFunctionalInterface getCustomFunctionalInterface(){
return (int a,int b)->{return a+b;};
}
CustomFunctionalInterface c = getCustomFunctionalInterface();
System.out.println("result = " + c.add(11, 22));
13、Stream流
13.1 什么是Stream流?
-
在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的概念:Stream流。
-
作用:简化了操作集合和数组的API,结合lambda表达式。
Stream流的思想和使用步骤:
1.先得到集合或者数组的Stream流(Stream流相当于一根传送带)
2.把元素放上去。
3.然后就用这个Stream流简化的API来方便的操作元素。
13.2 Stream流的获取
Stream操作集合或者数组的第一步是先得到stream流,然后才能使用流的功能。
1、集合获取Stream流的方式:
集合获取Stream的方式是通过调用stream()方法实现的。
名称 | 说明 |
---|---|
default Stream stream() | 获取当前集合对象的stream流 |
2、数组获取Stream流的方式:
名称 | 说明 |
---|---|
public static Stream stream(T[ ] array) | 获取当前数组的Stream流 |
public static Stream of(T… vilues) | 获取当前数组/可变数据的stream流 |
示例:
// Collection集合获取Stream流
Collection<String> list = new ArrayList<>();
Stream<String> s = list.stream();
// Map集合获取Stream流
Map<String, Integer> maps = new HashMap<>();
Stream<String> keyStream = maps.keySet().stream();//键流
Stream<Integer> valueStream = maps.values().stream();//值流
Stream<Map.Entry<String, Integer>> keyAndValueStream = maps.entrySet().stream(); // 键值对流(整体)
// 数组获取Stream流
IntStream arrayStream1 = Arrays.stream(new int[]{11, 2, 3, 4, 5});
Stream<int[]> arrayStream2 = Stream.of(new int[]{11, 2, 3, 4, 5});
13.3 Stream流常用API(中间操作方法)
- filter:过滤元素(过滤条件)
- forEach:遍历
- count:统计个数,返回值long类型。
- limit:取前几个元素
- skip:跳过前几个元素
- map:加工方法
- concat:合并流
- distinct:去重复
public class StreamDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("张珍珍");
list.add("赵六");
list.add("陈七");
list.add("张火火");
/*
filter:对流中的数据进行过滤
Stream<T> filter(Predicate<? super T> predicate);
*/
// list.stream().filter(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.startsWith("张"); //保留姓张的数据
// }
// }).forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
/*
count:统计个数
*/
long count = list.stream().filter(s -> s.length() == 3).count();
System.out.println("count = " + count);
/*
limit():取前几个元素
*/
// list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);
/*
skip():跳过前几个元素
*/
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(s -> System.out.println(s));
/*
map加工方法
map(new Function<原材料类型, 加工后的结果类型>())
*/
// 给集合前面都加上hello
list.stream().map(s -> "hello" + s).forEach(s -> System.out.println(s));
// 把所有名称都加工成一个学生对象
// list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
list.stream().map(Student::new).forEach(System.out::println);
/*
concat:合并流
不同类型流用Object接
*/
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> s2 = Stream.of("Java","PHP","C++","Go");
Stream<String> s3= Stream.concat(s1,s2);
s3.forEach(System.out::println);
}
}
class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student() {
}
public Student(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
注意事项:
-
中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
-
在Stream流中无法直接修改集合、数组中的数据。
13.4 Stream流常见的终结操作方法
注意:终结操作方法,调用完成之后流就无法继续使用了,原因是它不会返回Stream流。
小结: 终结方法和非终结方法的含义?
调用终结方法后流不可以继续使用,调用非终结方法会返回新的流,支持链式编程。
13.5 Stream流的收集
用Stream流操作集合、数组,操作的结果数据恢复到集合或者数组中去。