1、类在内存中的生命周期:加载–>使用–>卸载
2、类的加载又分为三个阶段:
(1)加载:load
(2)连接:link
①验证:校验合法性
②准备:准备对应的内存(方法区),创建Class对象,为类变量赋默认值,为静态常量赋初始值。
③解析:把字节码中的符号引用替换为对应的直接地址引用
(3)初始化:initialize(类初始化),大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化。
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对象。
应用程序类加载器 把 扩展类加载器视为父加载器,
扩展类加载器 把 引导类加载器视为父加载器。
不是继承关系,是组合的方式实现的。
相关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);
}
}
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)
示例代码获取常规信息:
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);
}
}
}
}
两种方式:
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);
}
}
(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);
}
}
(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);
}
}
示例代码获取泛型父类信息:
/* Type:
* (1)Class
* (2)ParameterizedType
* 例如:Father
* ArrayList
* (3)TypeVariable
* 例如:T,U,E,K,V
* (4)WildcardType
* 例如:
* ArrayList>
* ArrayList super 下限>
* ArrayList extends 上限>
* (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>{
}
示例代码读取注解信息:
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{
}
Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。
lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。
其实只要满足“SAM”特征的接口都可以称为函数式接口,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。
之前学过的接口中,是函数式接口的有:Runnable,Comparator,FileFilter
这类接口的抽象方法特点:有形参,但是返回值类型是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值 |
这类接口的抽象方法特点:无参,但是无返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
Supplier | T get() | 返回一个对象 |
BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
DoubleSupplier | double getAsDouble() | 返回一个double值 |
IntSupplier | int getAsInt() | 返回一个int值 |
LongSupplier | long getAsLong() | 返回一个long值 |
这里接口的抽象方法特点:有参,但是返回值类型是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值 |
这类接口的抽象方法特点:既有参数又有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
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结果 |
Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。
其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”
Lambda表达式语法格式
(形参列表) -> {Lambda体}
说明:
优化:Lambda表达式可以精简
示例代码:
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 super T> 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 super T> 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));
}
}
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);
}
}
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。
1、创建 Stream方式一:通过集合
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
public default Stream stream() : 返回一个顺序流
public default Stream parallelStream() : 返回一个并行流
2、创建 Stream方式二:通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array):返回一个整型数据流
public static LongStream stream(long[] array):返回一个长整型数据流
public static DoubleStream stream(double[] array):返回一个浮点型数据流
3、创建 Stream方式三:通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
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();
}
}
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
方 法 | 描 述 |
---|---|
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);
}
}
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: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);
}
}
到目前为止,臭名昭著的空指针异常是导致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 extends T> other) :
如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替
(4) T orElseThrow(Supplier extends X> exceptionSupplier)
如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException
3、其他方法
(1)boolean isPresent() :判断Optional容器中的值是否存在
(2)void ifPresent(Consumer super T> consumer) :
判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做
(3) Optional map(Function super T,? extends U> 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 + "]";
}
}