1. Lambda
1.1 函数式编程思想概述
- 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“数据做操作“
- 面向对象思想强调“必须通过对象的形式来做事情”
- 函数式思想则尽量忽略面向对像的复杂语法:“强调做什么,而不是以什么形式去做”
- 而我们要学习的Lambda表达式就是函数式思想的体现
1.2 体验lambda表达式
需求:启动一个线程,在控制台输出一句话:多线程程序启动了
方式1:
- 定义一个类MyRunnable:实现Runnable接口,重写run() 方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable的对象作为构造参数传递
- 启动线程
方式2:
- 匿名内部类的方式改进
方式3:
- Lambda表达式的方式改进
//1.创建MyRunnable类
package demo_01;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("多线程启动了");
}
}
//2.实现类
package demo_01;
//需求:启动一个线程,在控制台输出一句话:多线程程序启动了
public class LambdaDemo {
public static void main(String[] args) {
//实现类的方式实现需求
/* MyRunnable my = new MyRunnable();
Thread th = new Thread(my);
th.start();*/
//匿名内部类的方式改进
/*new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程启动了");
}
}).start();*/
//Lambda表达式的方式改进
new Thread(() -> {
System.out.println("多线程启动");
}).start();
}
}
1.3 Lambda表达式的标准格式
匿名内部类中重写run() 方法的代码分析:
- 方法形式参数为空,说明调用方法时不需要传递参数
- 方法返回值类型为void,说明方法执行没有结果返回
- 方法体中的内容,是我们具体要做的事情
//匿名内部类的方式改进
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程启动了");
}
}).start();
Lambda表达式的代码分析:
- () :里面没有内容,可以看成是方法形式参数为空
- -> :用箭头指向后面要做的事情
- {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
组成Lambda表达式的三要素:形式参数,箭头,代码块
//Lambda表达式的方式改进
new Thread(() -> {
System.out.println("多线程启动");
}).start();
Lambda表达式的格式:
- 格式:(形式参数)-> {代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
1.4 Lambda表达式的使用
Lambda表达式的使用前提:
- 有一个接口
- 接口中有且仅有一个抽象方法
练习1:
- 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
- 定义一个测试类(EatableDemo),在测试类中提供两个方法:
一个方法是:useEatable(Eatable e)
一个方法是主方法,在主方法中调用useEatable方法
//1.创建接口
package demo_02;
public interface Eatable {
void eat();
}
//2.接口实现类
package demo_02;
public class EatableLmpl implements Eatable{
@Override
public void eat() {
System.out.println("吃水果");
}
}
//3.测试类
package demo_02;
/*
- 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
- 定义一个测试类(EatableDemo),在测试类中提供两个方法:
一个方法是:useEatable(Eatable e)
一个方法是主方法,在主方法中调用useEatable方法
*/
public class EatableDemo {
public static void main(String[] args) {
//接口实现类方法
Eatable el = new EatableLmpl();
useEatable(el);
//匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("吃水果");
}
});
//lambda表达式
useEatable(()->{
System.out.println("吃水果");
});
}
public static void useEatable(Eatable e){
e.eat();
}
}
练习2:
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s):
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法
一个方法是:useFlyable(Flyable f)
一个方法是主方法,在主方法中调用useFlyable方法
//1.创建Flyable接口
package demo_03;
public interface Flyable {
void fly(String s);
}
//测试类
package demo_03;
/*
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s):
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法
一个方法是:useFlyable(Flyable f)
一个方法是主方法,在主方法中调用useFlyable方法
*/
public class FlyableDemo {
public static void main(String[] args) {
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("万物复苏");
}
});
//lanbada表达式
useFlyable((s -> {
System.out.println("lambada引用");
}));
}
public static void useFlyable(Flyable f){
f.fly("出暖花开,生机勃勃");
}
}
练习3:
- 定义一个接口(Addable),里面定义一个抽象方法:int add(intx, inty);
- 定义一个测试类(AddableDemo),在测试类中提供两个方法:
一个方法是:useAddable(Addable a
一个方法是主方法,在主方法中调用useAddable方法
//1.创建接口
package demo_04;
public interface AddAble {
int add(int x,int y);
}
//2.测试类
package demo_04;
/*
- 定义一个接口(Addable),里面定义一个抽象方法:int add(intx, inty);
- 定义一个测试类(AddableDemo),在测试类中提供两个方法:
一个方法是:useAddable(Addable a
一个方法是主方法,在主方法中调用useAddable方法
*/
public class AddAbleDemo {
public static void main(String[] args) {
//调用useAddable方法
useAddable(((x, y) -> {
return x + y;
}));
}
public static void useAddable(AddAble a){
int sum = a.add(10,20);
System.out.println(sum);
}
}
1.5 Lambda表达式的省略模式
省略规则:
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
//1.定义Addable接口
package demo_05;
public interface Addable {
int add(int x, int y);
}
//2.创建Flyanle接口
package demo_05;
public interface Flyable {
void fly(String s);
}
//3.测试类
package demo_05;
import demo_03.Flyable;
import demo_04.AddAble;
public class LambdaDemo {
public static void main(String[] args) {
//参数类型可以省略
useAddable((x,y)->{
return x + y;
});
useFlyable((s) -> {
System.out.println(s);
});
//如果参数有且仅有一个,那么小括号也可以省略
useFlyable(s -> System.out.println(s));
//如果代码语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s));
//如果代码语句只有一条,可以省略大括号和分号,如果有return,也可以省略掉
useAddable((x,y)-> x + y);
}
public static void useFlyable(Flyable f){
f.fly("春暖花开,万物复苏");
}
public static void useAddable(AddAble a){
int sum = a.add(20,30);
System.out.println(sum);
}
}
1.6 Lambda表达式的注意事项
注意事项:
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
根据局部变量的赋值得知Lambda对应的接口:Runnable r=()->System.out.println(“Lambda表达式”);
根据调用方法的参数得知Lambda对应的接口:new Thread(()->System.out.println(“Lambda表达式”).start();
1.7 Lambda表达式和匿名内部类的区别
1.所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
2.使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中有多个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
3.实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
3.方法引用
3.1方法引用符
**方法引用符"::"**该符号为引用运算符,而它所在的表达式被称为方法引用
体验方法引用中的代码:
- Lambda表达式:usePrintable(s->System.out.println(s));
- 分析:拿到参数s之后通过Lambda表达式,传递给System.out.println方法去处理
- 方法引用:usePrintable(System.out::printIn);
- 分析:直接使用System.out中的println方法来取代Lambda,代码更加的简洁推导与省略
- 如果使用Lambda,那么根据“可推导就是可省略”的原则侧,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
- 如果使用方法引用,也是同样可以根据上下文进行推导
- 方法引用是Lambda的李生兄弟
//1.创建接口
package demo_01;
public interface Printable {
void printInt(int i);
}
//2.测试类
package demo_01;
public class PrintableDemo {
public static void main(String[] args) {
//Lambda表达式调用
usePrintable(i -> System.out.println(i));
//方法引用
usePrintable(System.out::println);
}
private static void usePrintable(Printable p){
p.printInt(666);
}
}
3.2 引用类方法
引用类方法,其实就是引用类的静态方法
-
格式:类名::静态方法
-
范例:Integer::parselnt
Integer类的方法:public static int parselnt(String s)将此String转换为int类型数据
//1.
package demo_02;
public interface Converter {
int convert(String s);
}
//2.
package demo_02;
public class ConverterDemo {
public static void main(String[] args) {
//Lambda表达式调用
useConvert(s -> Integer.parseInt(s));
useConvert((String s)->{
int i = Integer.parseInt(s);
return i;
});
//引用类方法
useConvert(Integer::parseInt);
//Lambda表达式被类方法替代的时候,他的形式参数全部传递给静态方法作为参数
}
private static void useConvert(Converter c){
int result = c.convert("666");
System.out.println(result);
}
}
3.3 引用对象的实例方法
引用对象的实例方法,其实就引用类中的成员方法
- 格式:对象::成员方法
- 范例:“HelloWorld”::toUpperCase
String类中的方法:public String toUpperCase0将此String.所有字符转换为大写
//1.创建对象
package demo_03;
public class PrintString {
public void printUpper(String s){
//toUpperCase()把字符转化为大写
String s1 = s.toUpperCase();
System.out.println(s1);
}
}
//2.创建接口
package demo_03;
public interface Printer {
void printUpperCase(String s);
}
//3.测试类
package demo_03;
public class PrinterDemo {
public static void main(String[] args) {
//Lambda表达式
usePrinter(s -> System.out.println(s.toUpperCase()));
//引用对象的实例方法
PrintString ps = new PrintString();
usePrinter(ps::printUpper);
}
private static void usePrinter(Printer p){
p.printUpperCase("helloword");
}
}
3.4 引用类的实例方法
引用类的实例方法,其实就是引用类中的成员方法
- 格式:类::成员方法
- 范例:String::substring
//1.创建接口
package demo_04;
public interface MyString {
String mySubString(String s,int x,int y);
}
//2.测试类
package demo_04;
public class MyStringDemo {
public static void main(String[] args) {
/*useMyString((String s,int x, int y)->{
return s.substring(x, y);
});*/
// useMyString((s,x,y)->s.substring(x,y));
//引用类的实例方法
useMyString(String::substring);
}
private static void useMyString(MyString my){
String s = my.mySubString("helloword", 2, 5);
System.out.println(s);
}
}
3.5 引用构造器
引用构造器,其实就是引用构造方法
- 格式:类::new
- 范例:Student::new
//1.创建接口
package demo_05;
public interface StudentBuilder {
Student build(String name,int age);
}
//2.创建学生类
//3.测试类
package demo_05;
public class StudentDemo {
public static void main(String[] args) {
useStudentBuilder((name, age) -> new Student(name,age));
//引用构造器
useStudentBuilder(Student::new);
}
private static void useStudentBuilder(StudentBuilder sb){
Student s1 = sb.build("张三", 18);
System.out.println(s1.getName()+s1.getAge());
}
}
5. 类加载器
5.1类加载
当程序要使用某个类时,如果该类还未被加截到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
类的加载
- 就是指将class文件读入内存,并为之创建一个java.lang.Class对象
- 任何类被使用时,系统都会为之建立一个java.lang.Class对象
类的连接
- 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
- 解析阶段:将类的二进制数据中的符号引用替换为直接引用
类的初始化
- 在该阶段,主要就是对类变量进行初始化
类的初始化步骤
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
- 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
类的初始化时机:
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
5.2 类加载器
类加载器的作用
- 负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
- 虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行
JVM的类加载机制
- 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
- 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
- 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
ClassLoader:是负责加载类的对象
Java运行时具有以下内置类加载器
- Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null,并且没有父nul川
- Platform class loader:平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的Java SE平台API,
其实现类和DK特定的运行时类 - System class loader:它也被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用于定义应用程序类路径模块路径和JDK特定工具上的类
- 类加载器的继承关系:System的父加载器为Platform,而Platform的父加载器为Bootstrap
ClassLoader中的两个方法
- static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
- ClassLoader getParent():返回父类加载器进行委派
6 反射
6.1 反射概述
Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展
练习
//三种方式获取Class对象:
//1.使用类的class,属性来获取该类对应的Class对象。举例:Student.class将会返回Student类对应的Class对象
//2.调用对象的getClass()方法,返回该对象所属类对应的Class对象
//该方法是Object类中的方法,所有的Java对象都可以调用该方法
//3.使用CLass类中的静态方法ForName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,
//1.创建Student类
//2.测试类
package demo_01;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
//1.使用类的class,属性来获取该类对应的Class对象。举例:Student.class将会返回Student类对应的Class对象
Class<Student> c1 = Student.class;
System.out.println(c1);
//2.调用对象的getClass()方法,返回该对象所属类对应的Class对象
Student s1 = new Student();
Class<? extends Student> c2 = s1.getClass();
System.out.println(c2 == c1);
//3.使用CLass类中的静态方法ForName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,
Class<?> c3 = Class.forName("demo_01.Student");
System.out.println(c3 == c2);
}
}
6.2 反射获取构造方法并使用
Class类中用于获取构造方法的方法:
Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
Constructor<T> getConstructor((Class<?>parameterTypes):返回单个公共构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>parameterTypes):返回单个构造方法对像
Constructora类中用于创建对象的方法
T newInstance(Object.initargs):根据指定的构造方法创建对象
6.2 练习1:反射获取构造方法的使用
练习1:通过反射实现如下操作
- Student s=new Student(“林青霞”,30,“西安”);
- System.out.println(s);
- 基本数据类型也可以通过.class得到对应的Class类型
package test;
import java.lang.reflect.Constructor;
public class Test_01 {
public static void main(String[] args) throws Exception{
//获取class对象
Class<?> c = Class.forName("demo_01.Student");
//Constructor<T> getConstructor(类<?>... parameterTypes)
//返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。
Constructor<?> cos = c.getConstructor(String.class, int.class, String.class);
//T newInstance(Object... initargs)
//使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
Object obj = cos.newInstance("林青霞", 18, "西安");
System.out.println(obj);
}
}
练习2:通过反射实现如下操作
Student s=new Student(“林青霞”);
System.out.printIn(s);
public void setAccessible(boolean flag):值为true,取消访问检查
package test;
import java.lang.reflect.Constructor;
public class Test_02 {
public static void main(String[] args) throws Exception{
//获取class对象
Class<?> c = Class.forName("demo_01.Student");
//Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
//返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。
Constructor<?> con = c.getDeclaredConstructor(String.class);
//暴力反射
//public void setAccessible(boolean flag):值为true,取消访问检查
con.setAccessible(true);
Object obj = con.newInstance("林青霞");
System.out.println(obj);
}
}
6.3 反射获取成员变量
Class类中用于获取成员变量的方法
- Field[] getFields():返回所有公共成员变量对象的数组
- Field[] getDeclaredFields():返回所有成员变量对象的数组
- Field getField(String name):返回单个公共成员变量对象
- Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于给成员变量赋值的方法
- void set(Object obj,Object value):给obj对象的成员变量赋值为value
练习:获取成员变量
要求:
Student s new Student();
s.name="林青霞";
s.age = 30;
s.address="西安";
System.out.printin(s);
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test_03 {
public static void main(String[] args) throws Exception{
//获取Class对象
Class<?> c = Class.forName("demo_01.Student");
//获取无参构造方法对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//获取成员变量name
Field name = c.getDeclaredField("name");
name.setAccessible(true);
name.set(obj,"林青霞");
System.out.println(obj);
//获取成员变量age
Field age = c.getDeclaredField("age");
age.setAccessible(true);
age.set(obj,30);
System.out.println(obj);
//获取成员变量address
Field address = c.getDeclaredField("address");
address.setAccessible(true);
address.set(obj,"西安");
System.out.println(obj);
}
}
6.4 反射获取成员方法
Class类中用于获取成员方法的方法
- Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
- Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
- Method getMethod(String name,Class<?>.parameterTypes):返回单个公共成员方法对象
- Method getDeclaredMethod(String name,Class<?>.parameterTypes):返回单个成员方法对象
Method类中用于调用成员方法的方法
- Object invoke(Object obj,Object.args):调用obj对象的成员方法,参数是args,返回值是Object类型
练习
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/*
练习:通过反射实现如下操作:
Student s new Student();
s.method1();
s.method2("林青霞");
String ss=s.method3("林青霞",30);
System.out.printIn(ss);
s.function();
*/
public class Test_04 {
public static void main(String[] args) throws Exception{
//获取Class对象
Class<?> c = Class.forName("demo_01.Student");
//获取无参构造方法对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//获取方法 s.method1();
Method m1 = c.getMethod("method1");
m1.invoke(obj);
//s.method2("林青霞");
Method m2 = c.getMethod("method2", String.class);
m2.invoke(obj,"林青霞");
//String ss=s.method3("林青霞",30);
Method m3 = c.getMethod("method3", String.class, int.class);
Object result = m3.invoke(obj, "林青霞", 18);
System.out.println(result);
//s.function();
Method m4 = c.getDeclaredMethod("function");
m4.setAccessible(true);
m4.invoke(obj);
}
}
6.5 练习:越过泛型检查
练习1:我有一个ArrayList.集合,现在我想在这个集合中添加一个符串数据,如何实现?
package test;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Test_越过泛型检查 {
public static void main(String[] args) throws Exception{
//创建集合
ArrayList<Integer> array = new ArrayList<Integer>();
//获取Class对象
Class<? extends ArrayList> c = array.getClass();
Method m = c.getDeclaredMethod("add", Object.class);
//添加元素
m.invoke(array,"hello");
m.invoke(array,"world");
m.invoke(array,"java");
System.out.println(array);
}
}
6.6 练习:运行配置文件指定内容
package com.itheima.demo09Reflect;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
import java.util.ResourceBundle;
public class Demo09 {
public static void main(String[] args) throws Exception {
//方式一
/*// 1.通过Properties加载配置文件
Properties pp = new Properties();
pp.load(new FileReader("day_13\\src\\config.properties"));
System.out.println("pp = " + pp);
// 2.得到类名和方法名
String className = pp.getProperty("classname"); // com.itheima.demo09Reflect.Teacher
String methodName = pp.getProperty("methodname"); // teach*/
//方式二
ResourceBundle config = ResourceBundle.getBundle("config"); //读取config.properties
//得到类名和方法
String classname = config.getString("className");
String methodname = config.getString("methodName");
// 3.通过类名反射得到Class对象
Class<?> cls = Class.forName(classname);
// 4.通过Class对象创建一个对象
Object obj = cls.getConstructor().newInstance();
// 5.通过Class对象得到方法
Method method = cls.getMethod(methodname);
// 6.调用方法
method.invoke(obj);
}
}
//2.Worker类
package com.itheima.demo09Reflect;
public class Worker {
public void working() {
System.out.println("我是十年后来的工人,我跑的很开心!");
}
}