Java基础(五)——反射、lambda、stream

Java基础

  • 第十六章 反射
    • 16.1 类加载
    • 16.2 javalang.Class类
    • 16.3 反射的应用
      • 16.3.1 获取类型的详细信息
      • 16.3.2 创建任意引用类型的对象
      • 16.3.3 操作任意类型的属性
      • 16.3.4 调用任意类型的方法
      • 16.3.5 获取泛型父类信息
      • 16.3.6 读取注解信息
  • 第十七章 Java8
    • 17.1 Lambda表达式与函数式接口
      • 17.1.1 函数式接口
        • **1、消费型接口**
        • **2、供给型接口**
        • **3、判断型接口**
        • **4、功能型接口**
      • 17.1.2 Lambda表达式语法
      • 17.1.3 方法引用与构造器引用
    • 17.2 StreamAPI
      • 17.2.1 创建Stream
      • 17.2.2 中间操作
      • 17.2.3 终结操作
    • 17.3 Optional类

第十六章 反射

16.1 类加载

1、类在内存中的生命周期:加载–>使用–>卸载

2、类的加载又分为三个阶段:

(1)加载:load

(2)连接:link

①验证:校验合法性

②准备:准备对应的内存(方法区),创建Class对象,为类变量赋默认值,为静态常量赋初始值。

③解析:把字节码中的符号引用替换为对应的直接地址引用

(3)初始化:initialize(类初始化),大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化。

Java基础(五)——反射、lambda、stream_第1张图片

3、哪些会导致类的初始化?

(1)主方法所在的类,要先初始化

(2)第一次使用某个类型就是在new它的对象,此时这个类没有初始化的话,先完成类初始化再做实例初始化

(3)调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化

(4)子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类

(5)通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化

类初始化执行的是(),该方法由(1)类变量的显式赋值代码(2)静态代码块中的代码构成

class Father{
	static{
		System.out.println("main方法所在的类的父类(1)");//初始化子类时,会初始化父类
	}
}

public class TestClinit1 extends Father{
	static{
		System.out.println("main方法所在的类(2)");//主方法所在的类会初始化
	}
	
	public static void main(String[] args) throws ClassNotFoundException {
		new A();//第一次使用A就是创建它的对象,会初始化A类
		
		B.test();//直接使用B类的静态成员会初始化B类
		
		Class clazz = Class.forName("com.atguigu.test02.C");//通过反射操作C类,会初始化C类
	}
}
class A{
	static{
		System.out.println("A类初始化");
	}
}
class B{
	static{
		System.out.println("B类初始化");
	}
	public static void test(){
		System.out.println("B类的静态方法");
	}
}
class C{
	static{
		System.out.println("C类初始化");
	}
}

4、哪些使用类,但是不会导致类的初始化?

(1)使用某个类的静态的常量(static final)

(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化

(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化

public class TestClinit2 {
	public static void main(String[] args) {
		System.out.println(D.NUM);//D类不会初始化,因为NUM是final的
		
		System.out.println(F.num);
		F.test();//F类不会初始化,E类会初始化,因为num和test()是在E类中声明的
		
		//G类不会初始化,此时还没有正式用的G类
		G[] arr = new G[5];//没有创建G的对象,创建的是准备用来装G对象的数组对象
        //G[]是一种新的类型,是数组类想,动态编译生成的一种新的类型
        //G[].class
	}
}
class D{
	public static final int NUM = 10;
	static{
		System.out.println("D类的初始化");
	}
}
class E{
	static int num = 10;
	static{
		System.out.println("E父类的初始化");
	}
	public static void test(){
		System.out.println("父类的静态方法");
	}
}
class F extends E{
	static{
		System.out.println("F子类的初始化");
	}
}

class G{
	static{
		System.out.println("G类的初始化");
	}
}

5、类加载需要类加载器

(1)引导类加载器

​ 它负责加载jre/rt.jar核心库

​ 它本身不是Java代码实现的,也不是ClassLoader的子类,获取它的对象时往往返回null

(2)扩展类加载器

​ 它负责加载jre/lib/ext扩展库

​ 它是ClassLoader的子类

(3)应用程序类加载器

​ 它负责加载项目的classpath路径下的类

​ 它是ClassLoader的子类

(4)自定义类加载器

​ 当你的程序需要加载“特定”目录下的类,可以自定义类加载器;

​ 当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码

​ 后面会见到的自定义类加载器:tomcat中

6、Java系统类加载器的双亲委托模式

简单描述:

​ 下一级的类加载器,如果接到任务时,会先搜索是否加载过,如果没有,会先把任务往上传,如果都没有加载过,一直到根加载器,如果根加载器在它负责的路径下没有找到,会往回传,如果一路回传到最后一级都没有找到,那么会报ClassNotFoundException或NoClassDefError,如果在某一级找到了,就直接返回Class对象。

应用程序类加载器 把 扩展类加载器视为父加载器,

扩展类加载器 把 引导类加载器视为父加载器。

不是继承关系,是组合的方式实现的。

16.2 javalang.Class类

相关API(1)java.lang.Class(2)java.lang.reflect.*;

1、Class对象是反射的根源。

2、哪些类型可以获取Class对象,可以用代码示例

//(1)基本数据类型和void
例如:int.class
	 void.class
//(2)类和接口
例如:String.class
	Comparable.class
//(3)枚举
例如:ElementType.class
//(4)注解
例如:Override.class
//(5)数组
例如:int[].class

3、获取Class对象的四种方式
(1)类型名.class

要求编译期间已知类型

(2)对象.getClass()

只能获取已经存在的对象的运行时类型

(3)Class.forName(类型全名称)

可以获取编译期间未知的类型

(4)ClassLoader的类加载器对象.loadClass(类型全名称)

可以用自定义加载器对象加载指定路径下的类型

public class TestClass {
	@Test
	public void test05() throws ClassNotFoundException{
		Class c = TestClass.class;
		ClassLoader loader = c.getClassLoader();
		
		Class c2 = loader.loadClass("com.atguigu.test05.Employee");
		Class c3 = Employee.class;
		System.out.println(c2 == c3);
	}
	
	@Test
	public void test03() throws ClassNotFoundException{
		Class c2 = String.class;
		Class c1 = "".getClass();
		Class c3 = Class.forName("java.lang.String");
		
		System.out.println(c1 == c2);
		System.out.println(c1 == c3);
	}
}

16.3 反射的应用

16.3.1 获取类型的详细信息

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)

示例代码获取常规信息:

public class TestClassInfo {
	public static void main(String[] args) throws NoSuchFieldException, SecurityException {
		//1、先得到某个类型的Class对象
		Class clazz = String.class;
		//比喻clazz好比是镜子中的影子
		
		//2、获取类信息
		//(1)获取包对象,即所有java的包,都是Package的对象
		Package pkg = clazz.getPackage();
		System.out.println("包名:" + pkg.getName());
		
		//(2)获取修饰符
		//其实修饰符是Modifier,里面有很多常量值
		/*
		 * 0x是十六进制
		 * PUBLIC           = 0x00000001;  1    1
		 * PRIVATE          = 0x00000002;  2	10
		 * PROTECTED        = 0x00000004;  4	100
		 * STATIC           = 0x00000008;  8	1000
		 * FINAL            = 0x00000010;  16	10000
		 * ...
		 * 
		 * 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
		 * 
		 * mod = 17          0x00000011
		 * if ((mod & PUBLIC) != 0)  说明修饰符中有public
		 * if ((mod & FINAL) != 0)   说明修饰符中有final
		 */
		int mod = clazz.getModifiers();
		System.out.println(Modifier.toString(mod));
		
		//(3)类型名
		String name = clazz.getName();
		System.out.println(name);
		
		//(4)父类,父类也有父类对应的Class对象
		Class superclass = clazz.getSuperclass();
		System.out.println(superclass);
		
		//(5)父接口们
		Class[] interfaces = clazz.getInterfaces();
		for (Class class1 : interfaces) {
			System.out.println(class1);
		}
		
		//(6)类的属性,  你声明的一个属性,它是Field的对象
/*		Field clazz.getField(name)  根据属性名获取一个属性对象,但是只能得到公共的
		Field[] clazz.getFields();  获取所有公共的属性
		Field clazz.getDeclaredField(name)  根据属性名获取一个属性对象,可以获取已声明的
		Field[] clazz.getDeclaredFields()	获取所有已声明的属性
		*/
		Field valueField = clazz.getDeclaredField("value");
//		System.out.println("valueField = " +valueField);
		
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field field : declaredFields) {
			//修饰符、数据类型、属性名    
			int modifiers = field.getModifiers();
			System.out.println("属性的修饰符:" + Modifier.toString(modifiers));
			
			String name2 = field.getName();
			System.out.println("属性名:" + name2);
			
			Class<?> type = field.getType();
			System.out.println("属性的数据类型:" + type);
		}
		System.out.println("-------------------------");
		//(7)构造器们
		Constructor[] constructors = clazz.getDeclaredConstructors();
		for (Constructor constructor : constructors) {
			//修饰符、构造器名称、构造器形参列表  、抛出异常列表
			int modifiers = constructor.getModifiers();
			System.out.println("构造器的修饰符:" + Modifier.toString(modifiers));
			
			String name2 = constructor.getName();
			System.out.println("构造器名:" + name2);
			
			//形参列表
			System.out.println("形参列表:");
			Class[] parameterTypes = constructor.getParameterTypes();
			for (Class class1 : parameterTypes) {
				System.out.println(class1);
			}
		}
		System.out.println("=--------------------------------");
		//(8)方法们
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method method : declaredMethods) {
			//修饰符、返回值类型、方法名、形参列表 、异常列表 
			int modifiers = method.getModifiers();
			System.out.println("方法的修饰符:" + Modifier.toString(modifiers));
			
			Class<?> returnType = method.getReturnType();
			System.out.println("返回值类型:" + returnType);
			
			String name2 = method.getName();
			System.out.println("方法名:" + name2);
			
			//形参列表
			System.out.println("形参列表:");
			Class[] parameterTypes = method.getParameterTypes();
			for (Class class1 : parameterTypes) {
				System.out.println(class1);
			}
			
			//异常列表
			System.out.println("异常列表:");
			Class<?>[] exceptionTypes = method.getExceptionTypes();
			for (Class<?> class1 : exceptionTypes) {
				System.out.println(class1);
			}
		}
		
	}
}

16.3.2 创建任意引用类型的对象

两种方式:

1、直接通过Class对象来实例化(要求必须有无参构造)

2、通过获取构造器对象来进行实例化

方式一的步骤:

(1)获取该类型的Class对象(2)创建对象

方式二的步骤:

(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象

如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

示例代码:

public class TestNewInstance {
	@Test
	public void test3()throws Exception{
		//(1)获取Class对象
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		/*
		 * 获取Student类型中的有参构造
		 * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
		 * 例如:public Student(int id, String name) 
		 */
		//(2)获取构造器对象
		Constructor<?> constructor = clazz.getDeclaredConstructor(int.class,String.class);
		
		//(3)创建实例对象
		// T newInstance(Object... initargs)  这个Object...是在创建对象时,给有参构造的实参列表
		Object obj = constructor.newInstance(2,"张三");
		System.out.println(obj);
	}
	
	@Test
	public void test2()throws Exception{
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		//Caused by: java.lang.NoSuchMethodException: com.atguigu.test.Student.()
		//即说明Student没有无参构造,就没有无参实例初始化方法
		Object stu = clazz.newInstance();
		System.out.println(stu);
	}
	
	@Test
	public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//		AtGuigu obj = new AtGuigu();//编译期间无法创建
		
		Class<?> clazz = Class.forName("com.atguigu.test.AtGuigu");
		//clazz代表com.atguigu.test.AtGuigu类型
		//clazz.newInstance()创建的就是AtGuigu的对象
		Object obj = clazz.newInstance();
		System.out.println(obj);
	}
}

16.3.3 操作任意类型的属性

(1)获取该类型的Class对象
Class clazz = Class.forName(“com.atguigu.bean.User”);
(2)获取属性对象
Field field = clazz.getDeclaredField(“username”);
(3)创建实例对象
Object obj = clazz.newInstance();

(4)设置属性可访问

field.setAccessible(true);

(4)设置属性

field.set(obj,“chai”);
(5)获取属性值
Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示

示例代码:

public class TestField {
	public static void main(String[] args)throws Exception {
		//1、获取Student的Class对象
		Class clazz = Class.forName("com.atguigu.test.Student");
		
		//2、获取属性对象,例如:id属性
		Field idField = clazz.getDeclaredField("id");
		
		//3、创建实例对象,即,创建Student对象
		Object stu = clazz.newInstance();
		
		//如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
		idField.setAccessible(true);
		
		//4、获取属性值
		/*
		 * 以前:int 变量= 学生对象.getId()
		 * 现在:Object id属性对象.get(学生对象)
		 */
		Object value = idField.get(stu);
		System.out.println("id = "+ value);
		
		//5、设置属性值
		/*
		 * 以前:学生对象.setId(值)
		 * 现在:id属性对象.set(学生对象,值)
		 */
		idField.set(stu, 2);
		
		value = idField.get(stu);
		System.out.println("id = "+ value);
	}
}

16.3.4 调用任意类型的方法

(1)获取该类型的Class对象
Class clazz = Class.forName(“com.atguigu.service.UserService”);
(2)获取方法对象
Method method = clazz.getDeclaredMethod(“login”,String.class,String.class);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj,“chai”,"123);

如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

如果方法是静态方法,实例对象也可以省略,用null代替

示例代码:

public class TestMethod {
	@Test
	public void test()throws Exception {
		// 1、获取Student的Class对象
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		
		//2、获取方法对象
		/*
		 * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
		 * 
		 * 例如:void setName(String name)
		 */
		Method method = clazz.getDeclaredMethod("setName", String.class);
		
		//3、创建实例对象
		Object stu = clazz.newInstance();
		
		//4、调用方法
		/*
		 * 以前:学生对象.setName(值)
		 * 现在:方法对象.invoke(学生对象,值)
		 */
		method.invoke(stu, "张三");
		
		System.out.println(stu);
	}
}

16.3.5 获取泛型父类信息

示例代码获取泛型父类信息:

/* Type:
 * (1)Class
 * (2)ParameterizedType   
 * 		例如:Father
 * 			ArrayList
 * (3)TypeVariable
 * 		例如:T,U,E,K,V
 * (4)WildcardType
 * 		例如:
 * 		ArrayList
 * 		ArrayList
 * 		ArrayList
 * (5)GenericArrayType
 * 		例如:T[]
 * 	
 */
public class TestGeneric {
	public static void main(String[] args) {
		//需求:在运行时,获取Son类型的泛型父类的泛型实参
		
		//(1)还是先获取Class对象
		Class clazz = Son.class;//四种形式任意一种都可以
		
		//(2)获取泛型父类
//		Class sc = clazz.getSuperclass();
//		System.out.println(sc);
		/*
		 * getSuperclass()只能得到父类名,无法得到父类的泛型实参列表
		 */
		Type type = clazz.getGenericSuperclass();
		
		// Father属于ParameterizedType
		ParameterizedType pt = (ParameterizedType) type;
		
		//(3)获取泛型父类的泛型实参列表
		Type[] typeArray = pt.getActualTypeArguments();
		for (Type type2 : typeArray) {
			System.out.println(type2);
		}
	}
}
//泛型形参:
class Father<T,U>{
	
}
//泛型实参:
class Son extends Father<String,Integer>{
	
}

16.3.6 读取注解信息

示例代码读取注解信息:

public class TestAnnotation {
	public static void main(String[] args) {
		//需求:可以获取MyClass类型上面配置的注解@MyAnnotation的value值
		
		//读取注解
//		(1)获取Class对象
		Class<MyClass> clazz = MyClass.class;
		
		//(2)获取注解对象
		//获取指定注解对象
		MyAnnotation my = clazz.getAnnotation(MyAnnotation.class);
		
		//(3)获取配置参数值
		String value = my.value();
		System.out.println(value);
	}
}
//声明
@Retention(RetentionPolicy.RUNTIME)  //说明这个注解可以保留到运行时
@Target(ElementType.TYPE) //说明这个注解只能用在类型上面,包括类,接口,枚举等
@interface MyAnnotation{
	//配置参数,如果只有一个配置参数,并且名称是value,在赋值时可以省略value=
	String value();
}

//使用注解
@MyAnnotation("/login")
class MyClass{
	
}

第十七章 Java8

17.1 Lambda表达式与函数式接口

Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。

17.1.1 函数式接口

lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。

其实只要满足“SAM”特征的接口都可以称为函数式接口,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。

之前学过的接口中,是函数式接口的有:Runnable,Comparator,FileFilter

1、消费型接口

这类接口的抽象方法特点:有形参,但是返回值类型是void

接口名 抽象方法 描述
Consumer void accept(T t) 接收一个对象用于完成功能
BiConsumer void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

2、供给型接口

这类接口的抽象方法特点:无参,但是无返回值

接口名 抽象方法 描述
Supplier T get() 返回一个对象
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值

3、判断型接口

这里接口的抽象方法特点:有参,但是返回值类型是boolean结果。

接口名 抽象方法 描述
Predicate boolean test(T t) 接收一个对象
BiPredicate boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值

4、功能型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名 抽象方法 描述
Function R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

17.1.2 Lambda表达式语法

Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。

其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”

Lambda表达式语法格式

(形参列表) -> {Lambda体}

说明:

  • (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表),照抄
  • {Lambda体}就是实现这个抽象方法的方法体
  • ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)

优化:Lambda表达式可以精简

  • 当{Lambda体}中只有一句语句时,可以省略{}和{;}
  • 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么return也可以省略,但是如果{;}没有省略的话,return是不能省略的
  • 当(形参列表)的类型是已知的,那么形参的类型可以省略
  • 当(形参列表)的类型是已知的,并且形参个数只有一个,那么可以把数据类型和()一起省略,但是形参名不能省略
  • 当(形参列表)是空参时,()不能省略

示例代码:

public class TestLambdaGrammer {
	@Test
	public void test1(){
		//用Lambda表达式给Runnable接口的形参或变量赋值
		/*
		 * 确定两件事,才能写好lambda表达式
		 * (1)这个接口的抽象方法长什么样:
		 * 		public void run()
		 * (2)这个抽象方法的实现要干什么事
		 * 		例如:我要打印“hello lambda"
		 */
		Runnable r = () -> {System.out.println("hello lambda");};
	}
	
	@Test
	public void test2(){
		//lambda体省略了{;}
		Runnable r = () -> System.out.println("hello lambda");
	}
	
	@Test
	public void test3(){
		String[] arr = {"hello","Hello","java","chai"};
		
		//为arr数组排序,但是,想要不区分大小写
		/*
		 * public static  void sort(T[] a,Comparator c)
		 * 这里要用Lambda表达式为Comparator类型的形参赋值
		 * 
		 * 两件事:
		 * (1)这个接口的抽象方法:  int compare(T o1, T o2)
		 * (2)这个抽象方法要做什么事?
		 * 		例如:这里要对String类型的元素,不区分大小写的比较大小
		 */
//		Arrays.sort(arr, (String o1, String o2) -> {return o1.compareToIgnoreCase(o2);});
		
		//省略了{return ;}
//		Arrays.sort(arr, (String o1, String o2) ->  o1.compareToIgnoreCase(o2));
		
		//省略了两个String
		Arrays.sort(arr, (o1, o2) ->  o1.compareToIgnoreCase(o2));
		
		for (String string : arr) {
			System.out.println(string);
		}
	}
	
	@Test
	public void test4(){
		ArrayList<String> list = new ArrayList<>();
		list.add("hello");
		list.add("java");
		list.add("world");
		
		/*
		 * JDK1.8给Collection系列的集合,准确的讲是在Iterable接口中,增加了一个默认方法
		 * 		default void forEach(Consumer action) 
		 * 这个方法是用来遍历集合等的。代替原来的foreach循环的。
		 * 
		 * 这个方法的形参是Consumer接口类型,它是函数式接口中消费型接口的代表
		 * 我现在调用这个方法,想要用Lambda表达式为Consumer接口类型形参赋值
		 * 
		 * 两件事:
		 * (1)它的抽象方法:  void  accept(T t)
		 * (2)抽象方法的实现要完成的事是什么
		 * 		例如:这里要打印这个t
		 */
//		list.forEach((String t) -> {System.out.println(t);});
		
		//省略{;}
//		list.forEach((String t) -> System.out.println(t));
		
		//省略String
//		list.forEach((t) -> System.out.println(t));
		
		//可以省略形参()
		list.forEach(t -> System.out.println(t));
	}
}

17.1.3 方法引用与构造器引用

Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。

1、当Lambda表达式满足一些特殊的情况时,还可以再简化:

(1)Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

例如:System.out对象,调用println()方法来完成Lambda体

​ Math类,调用random()静态方法来完成Lambda体

(2)并且Lambda表达式的形参正好是给该方法的实参

例如:t->System.out.println(t)

​ () -> Math.random() 都是无参

2、方法引用的语法格式:

(1)实例对象名::实例方法

(2)类名::静态方法

(3)类名::实例方法

说明:

  • ::称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)

  • Lambda表达式的形参列表,全部在Lambda体中使用上了,要么是作为调用方法的对象,要么是作为方法的实参。

  • 在整个Lambda体中没有额外的数据。

3、构造器引用

(1)当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表。

(2) 当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度

4、构造器引用的语法格式:

  • 类名::new

  • 数组类型名::new

示例代码:

public class TestMethodReference {
	
	//这个方法是模仿HashMap中,把你指定的数组的长度纠正为2的n次方的代码
	//createArray()的作用是,创建一个长度为2的n次方的数组
	public <R> R[] createArray(Function<Integer,R[]> fun,int length){
		int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;
		return fun.apply(length);
	}
	
	@Test
	public void test6(){
		/*
		 * Function是一个函数式接口,可以用Lambda表达式赋值
		 * Function的抽象方法   R apply(T t)
		 * 
		 * createArray这个方法中用的是Function fun。说明T类型已经指定为Integer
		 * 说明
		 */
//		Function f = (Integer len) -> new String[len];
		
		//因为Lambda体是在创建一个数组对象完成的,而且Lambda表达式的形参正好是创建数组用的长度
		//通过构造器引用省略
		Function<Integer,String[]> f = String[]::new;
		String[] array = createArray(f, 10);
		
		System.out.println(array.length);//16
	}
	
	@Test
	public void test5(){
//		Supplier s = () -> new String();//通过供给型接口,提供一个空字符串对象
		
		//构造器引用
		Supplier<String> s = String::new;//通过供给型接口,提供一个空字符串对象
	}
	
	@Test
	public void test4(){
//		Runnable r = () -> System.out.println("hello lambda");
		Runnable r = System.out::println;//打印空行
		
		//不能简化方法引用,因为"hello lambda"这个无法省略
	}
	
	@Test
	public void test3(){
		String[] arr = {"Hello","java","chai"};
//		Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));
		
		//用方法引用简化
		/*
		 * Lambda表达式的形参,第一个(例如:s1),正好是调用方法的对象,剩下的形参(例如:s2)正好是给这个方法的实参
		 */
		Arrays.sort(arr, String::compareToIgnoreCase);
	}
	
	@Test
	public void test2(){
//		Stream stream = Stream.generate(() -> Math.random());
		
		//用方法引用简化
		Stream<Double> stream = Stream.generate(Math::random);
	}
	
	@Test
	public void test1(){
		List<Integer> list = Arrays.asList(1,3,4,8,9);
		
		//list.forEach(t -> System.out.println(t));
		
		//用方法再简化
		list.forEach(System.out::println);
	}
}

17.2 StreamAPI

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤:

1- 创建 Stream:通过一个数据源(如:集合、数组),获取一个流

2- 中间操作:中间操作是个操作链,对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

3- 终止操作:一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

Java基础(五)——反射、lambda、stream_第2张图片

17.2.1 创建Stream

1、创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • public default Stream stream() : 返回一个顺序流

  • public default Stream parallelStream() : 返回一个并行流

2、创建 Stream方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • public static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array):返回一个整型数据流

  • public static LongStream stream(long[] array):返回一个长整型数据流

  • public static DoubleStream stream(double[] array):返回一个浮点型数据流

3、创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T… values) : 返回一个顺序流

4、创建 Stream方式四:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • public static Stream iterate(final T seed, final UnaryOperator f):返回一个无限流

  • public static Stream generate(Supplier s) :返回一个无限流

public class Test07StreamCreate {
	@Test
	public void test06(){
		/*
		 * Stream iterate(T seed, UnaryOperator f)  
		 * UnaryOperator接口,SAM接口,抽象方法:
		 * 
		 * UnaryOperator extends Function
		 * 		T apply(T t)
		 */
		Stream<Integer> stream = Stream.iterate(1, num -> num+=2);
//		stream = stream.limit(10);
		stream.forEach(System.out::println);
	}
	
	@Test
	public void test05(){
		Stream<Double> stream = Stream.generate(Math::random);
		stream.forEach(System.out::println);
	}
	
	@Test
	public void test04(){
		Stream<Integer> stream = Stream.of(1,2,3,4,5);
		stream.forEach(System.out::println);
	}
	
	@Test
	public void test03(){
		String[] arr = {"hello","world"};
		Stream<String> stream = Arrays.stream(arr);
	}
	
	@Test
	public void test02(){
		int[] arr = {1,2,3,4,5};
		IntStream stream = Arrays.stream(arr);
	}
	
	@Test
	public void test01(){
		List<Integer> list = Arrays.asList(1,2,3,4,5);
		
		//JDK1.8中,Collection系列集合增加了方法
		Stream<Integer> stream = list.stream();
	}
}

17.2.2 中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

方 法 描 述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
distinct() 筛选,通过流所生成元素的equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
peek(Consumer action) 接收Lambda,对流中的每个数据执行Lambda体操作
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
public class Test08StreamMiddle {
	
	@Test
	public void test12(){
		String[] arr = {"hello","world","java"};
		Stream<String> flatMap = Arrays.stream(arr)
			.flatMap(t -> Stream.of(t.split("|")));//Function接口抽象方法 R apply(T t)  现在的R是一个Stream
		flatMap.forEach(System.out::println);
	}
	
	
	@Test
	public void test11(){
		String[] arr = {"hello","world","java"};
		
		Arrays.stream(arr)
		
			.map(t->t.toUpperCase())
			.forEach(System.out::println);
	}
	
	@Test
	public void test10(){
		Stream.of(1,2,3,4,5)
			.map(t -> t+=1)//Function接口抽象方法 R apply(T t)
			.forEach(System.out::println);
	}
	
	@Test
	public void test09(){
		//希望能够找出前三个最大值,前三名最大的,不重复
		Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
			.distinct()
			.sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator接口  int compare(T t1, T t2)
			.limit(3)
			.forEach(System.out::println);
	}
	
	@Test
	public void test08(){
		long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.distinct()
			.peek(System.out::println)  //Consumer接口的抽象方法  void accept(T t)
			.count();
		System.out.println("count="+count);
	}
	
	
	@Test
	public void test07(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.skip(5)
			.distinct()
			.filter(t -> t%3==0)
			.forEach(System.out::println);
	}

	@Test
	public void test06(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.skip(5)
			.forEach(System.out::println);
	}
	
	@Test
	public void test05(){
		Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7)
			.distinct()  //(1,2,3,4,5,6,7)
			.filter(t -> t%2!=0) //(1,3,5,7)
			.limit(3)
			.forEach(System.out::println);
	}
	
	
	@Test
	public void test04(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.limit(3)
			.forEach(System.out::println);
	}
	
	
	@Test
	public void test03(){
		Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
			.distinct()
			.forEach(System.out::println);
	}
	
	
	@Test
	public void test02(){
		Stream.of(1,2,3,4,5,6)
			.filter(t -> t%2==0)
			.forEach(System.out::println);
	}
	
	@Test
	public void test01(){
		//1、创建Stream
		Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
		
		//2、加工处理
		//过滤:filter(Predicate p)
		//把里面的偶数拿出来
		/*
		 * filter(Predicate p)
		 * Predicate是函数式接口,抽象方法:boolean test(T t)
		 */
		stream = stream.filter(t -> t%2==0);
		
		//3、终结操作:例如:遍历
		stream.forEach(System.out::println);
	}
}


17.2.3 终结操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。流进行了终止操作后,不能再次使用。

方法 描述
boolean allMatch(Predicate p) 检查是否匹配所有元素
boolean anyMatch(Predicate p) 检查是否至少匹配一个元素
boolean noneMatch(Predicate p) 检查是否没有匹配所有元素
Optional findFirst() 返回第一个元素
Optional findAny() 返回当前流中的任意元素
long count() 返回流中元素总数
Optional max(Comparator c) 返回流中最大值
Optional min(Comparator c) 返回流中最小值
void forEach(Consumer c) 迭代
T reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
U reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
R collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。

public class Test09StreamEnding {
	
	@Test
	public void test14(){
		List<Integer> list = Stream.of(1,2,4,5,7,8)
				.filter(t -> t%2==0)
				.collect(Collectors.toList());
		
		System.out.println(list);
	}
	
	
	@Test
	public void test13(){
		Optional<Integer> reduce = Stream.of(1,2,4,5,7,8)
			 .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator接口   T apply(T t1, T t2)
		System.out.println(reduce);
	}
	
	@Test
	public void test12(){
		Integer reduce = Stream.of(1,2,4,5,7,8)
			 .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator接口   T apply(T t1, T t2)
		System.out.println(reduce);
	}
	
	@Test
	public void test11(){
		Optional<Integer> max = Stream.of(1,2,4,5,7,8)
				.max((t1,t2) -> Integer.compare(t1, t2));
		System.out.println(max);
	}
	
	@Test
	public void test10(){
		Optional<Integer> opt = Stream.of(1,2,4,5,7,8)
				.filter(t -> t%3==0)
				.findFirst();
		System.out.println(opt);
	}
	
	@Test
	public void test09(){
		Optional<Integer> opt = Stream.of(1,2,3,4,5,7,9)
				.filter(t -> t%3==0)
				.findFirst();
		System.out.println(opt);
	}
	
	@Test
	public void test08(){
		Optional<Integer> opt = Stream.of(1,3,5,7,9).findFirst();
		System.out.println(opt);
	}
	
	@Test
	public void test04(){
		boolean result = Stream.of(1,3,5,7,9)
			.anyMatch(t -> t%2==0);
		System.out.println(result);
	}
	
	
	@Test
	public void test03(){
		boolean result = Stream.of(1,3,5,7,9)
			.allMatch(t -> t%2!=0);
		System.out.println(result);
	}
	
	@Test
	public void test02(){
		long count = Stream.of(1,2,3,4,5)
				.count();
		System.out.println("count = " + count);
	}
	
	
	@Test
	public void test01(){
		Stream.of(1,2,3,4,5)
				.forEach(System.out::println);
	}
}


17.3 Optional类

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

它的API:

1、如何创建Optional对象?或者说如何用Optional来装值对象或null值

(1)static Optional empty() :用来创建一个空的Optional

(2)static Optional of(T value) :用来创建一个非空的Optional

(3)static Optional ofNullable(T value) :用来创建一个可能是空,也可能非空的Optional

2、如何从Optional容器中取出所包装的对象呢?

(1)T get() :要求Optional容器必须非空

T get()与of(T value)使用是安全的

(2)T orElse(T other) :

orElse(T other) 与ofNullable(T value)配合使用,

如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替

(3)T orElseGet(Supplier other) :

如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替

(4) T orElseThrow(Supplier exceptionSupplier)

如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

3、其他方法

(1)boolean isPresent() :判断Optional容器中的值是否存在

(2)void ifPresent(Consumer consumer) :

判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做

(3) Optional map(Function mapper)

判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做

public class TestOptional {
	@Test
	public void test9(){
		String str = "hello";
		Optional<String> opt = Optional.ofNullable(str);
		Optional<String> opt2 = opt.filter(s->s.length()>5);
		System.out.println(opt2);
	}
	
	
	@Test
	public void test8(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
		System.out.println(string);
	}
	
	
	@Test
	public void test7(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElseGet(String::new);
		System.out.println(string);
	}
	
	@Test
	public void test6(){
		String str = "hello";
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElse("atguigu");
		System.out.println(string);
	}
	
	@Test
	public void test5(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
	}
	
	@Test
	public void test4(){
		String str = "hello";
		Optional<String> opt = Optional.of(str);

		String string = opt.get();
		System.out.println(string);
	}
	
	
	@Test
	public void test3(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		System.out.println(opt);
	}
	
	@Test
	public void test2(){
		String str = "hello";
		Optional<String> opt = Optional.of(str);
		System.out.println(opt);
	}
	
	
	@Test
	public void test1(){
		ArrayList<Student> list = new ArrayList<>();
		list.add(new Student("张三", 23));
		//...
		
		//取出流中第一个年龄大于30岁的学生
		Optional<Student> opt = list.stream()
			.filter(s -> s.getAge()>30)
			.findFirst();
	
		
		//打印该学生的年龄
		Student stu = opt.orElse(new Student());
		System.out.println("学生的年龄:" + stu.getAge());
	}
}
class Student{
	private String name;
	private int age;
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Student() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	
}
    

你可能感兴趣的:(学习笔记,java,stream,反射)