第八章 枚举与注解
8.1 枚举
1、枚举(JDK1.5引入的)
枚举类型的对象是有限、固定的几个常量对象。
2、语法格式
//形式一:枚举类型中只有常量对象列表
【修饰符】 enum 枚举类型名{
常量对象列表
}
//形式二:枚举类型中只有常量对象列表
【修饰符】 enum 枚举类型名{
常量对象列表;
其他成员列表;
}
说明:常量对象列表必须在枚举类型的首行
回忆:首行
(1)super()或super(实参列表):必须在子类构造器的首行
(2)this()或this(实参列表):必须在本类构造器的首行
(3)package 包; 声明包的语句必须在源文件.java的代码首行
(4)枚举常量对象列表必须在枚举类型的首行
3、在其他类中如何获取枚举的常量对象
//获取一个常量对象
枚举类型名.常量对象名
//获取一个常量对象
枚举类型名.valueOf("常量对象名")
//获取所有常量对象
枚举类型名[] all = 枚举类型名.values();
4、枚举类型的特点
(1)枚举类型有一个公共的基本的父类,是java.lang.Enum类型,所以不能再继承别的类型
(2)枚举类型的构造器必须是私有的
(3)枚举类型可以实现接口
interface MyRunnable{
void run();
}
enum Gender implements MyRunnable{
NAN,NV;
public void run(){
//...
}
}
//或
enum Gender implements MyRunnable{
NAN{
public void run(){
//...
}
},NV{
public void run(){
//...
}
};
}
5、父类java.lang.Enum类型
(1)构造器
protected Enum(String name, int ordinal):由编译器自动调用
(2)String name():常量对象名
(3)int ordinal():返回常量对象的序号,第一个的序号是0
(4)String toString():返回常量对象名,如果子类想重写,需要手动
(5)int compareTo(Object obj):按照常量对象的顺序比较
8.2 注解
1、注解
它是代码级别的注释
2、标记符号:@
3、系统预定义的三个最基本的注解:
(1)@Override:表示某个方法是重写的方法
它只能用在方法上面,会让编译器对这个方法进行格式检查,是否满足重写的要求
(2)@SuppressWarnings(xx):抑制警告
(3)@Deprecated:表示xx已过时
4、和文档注释相关的注解
(1)文档注释
/**
文档注释
*/
(2)常见的文档注释
@author:作者
@since:从xx版本加入的
@see:另请参考
@param:形参
@return:返回值
@throws或@exception:异常
5、JUnit相关的几个注解
(1)@Test:表示它是一个单元测试方法
这个方法需要是:public void xxx(){}
(2)@Before:表示在每一个单元测试方法之前执行
这个方法需要是:public void xxx(){}
(3)@After:表示在每一个单元测试方法之后执行
这个方法需要是:public void xxx(){}
(4)@BeforeClass:表示在类初始化阶段执行,而且只执行一次
这个方法需要是:public static void xxx(){}
(3)@AfterClass:表示在类的“卸载”阶段执行,而且只执行一次
这个方法需要是:public static void xxx(){}
6、元注解
(1)@Target(xx):用它标记的注解能够用在xx位置
(xx):由ElementType枚举类型的10个常量对象指定,例如:TYPE,METHOD,FIELD等
例如:
@Target(ElementType.TYPE)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
import static java.lang.annotation.ElementType.*;
@Target({TYPE,METHOD,FIELD})
(2)@Retention(xx):用它标记的注解可以滞留到xx阶段
(xx):由RetentionPolicy枚举类型的3个常量对象指定,分别是:SOURCE,CLASS,RUNTIME
唯有RUNTIME阶段的注解才能被反射读取到
例如:
@Retention(RetentionPolicy.RUNTIME)
(3)@Documentd:用它标记的注解可以读取到API中
(4)@Inherited:用它标记的注解可以被子类继承
7、自定义注解
@元注解
【修饰符】 @interface 注解名{
}
@元注解
【修饰符】 @interface 注解名{
配置参数列表
}
配置参数的语法格式:
数据类型 配置参数名();
或
数据类型 配置参数名() default 默认值;
关于配置参数:
(1)配置参数的类型有要求:
八种基本数据类型、String、枚举、Class类型、注解、它们的数组。
(2)如果自定义注解声明了配置参数,那么在使用这个注解时必须为配置参数赋值,除非它有默认值
@自定义注解名(配置参数名1=值,配置参数名2=值。。。)
//如果配置参数类型是数组,那么赋值时,可以用{}表示数组
@自定义注解名(配置参数名1={值},配置参数名2=值。。。)
(3)如果配置参数只有一个,并且名称是value,那么赋值时可以省略value=
(4)如果读取这个注解时,要获取配置参数的值的话,可以当成方法一样来访问
自定义注解对象.配置参数();
第13章 泛型
13.1 泛型的概述
泛型:参数化类型
类型形参:,,,,,。。。。
类型实参:必须是引用数据类型,不能是基本数据类型
,,,<ArrayList>。。。
13.2 形式一:泛型类与泛型接口
1、声明语法格式:
【修饰符】 class 类名/接口<类型形参列表>{
}
【修饰符】 class 类名/接口<类型形参1 extends 父类上限>{
}
【修饰符】 class 类名/接口<类型形参1 extends 父类上限 & 父接口上限>{
}
在类名或接口名后面声明的泛型形参类型,可以在当前类或接口中使用,用作声明成员变量、方法的形参、方法的返回值。
但是不能用于静态成员上
2、使用语法格式
在(1)创建泛型类、泛型接口的对象时,为泛型形参指定具体类型
(2)在继承泛型类或实现泛型接口时,为泛型形参指定具体类型
示例代码
ArrayList<String> list = new ArrayList<String>();
ArrayList<String> list = new ArrayList<>();//JDK1.7之后可以省略
class MyStringArrayList extends ArrayList<String>{
}
class Employee implements Comparable<Employee>{
public int compareTo(Employee e){
}
}
Arrays.sort(数组, new Comparator<泛型实参>(){
public int compare(泛型实参类型 o1, 泛型实参类型 o2){
}
});
3、泛型如果没有指定,会被擦除,按照最左边的上限处理,如果没有指定上限,按照Object处理
13.3 形式二:泛型方法
1、声明的语法格式
【修饰符】 <泛型形参列表> 返回值类型 方法名(【数据形参列表】)【throws 异常列表】{}
【修饰符】 <泛型形参 extends 父类上限 & 父接口上限> 返回值类型 方法名(【数据形参列表】)【throws 异常列表】{}
(1)在方法返回值类型前面声明的泛型形参类型,只能在当前方法中使用,用于表示形参的类型或返回值类型,或方法局部变量的类型,和别的方法无关。
(2)泛型方法可以是静态方法,也可以是非静态方法
2、 使用
当调用方法,会根据具体的数据的实参的类型,来确定泛型实参的类型。
13.4 通配符?
(1)?:代表任意引用数据类型
(2)? extends 上限:代表上限本身或它的子类
(3)? super 下限:代表下限本身或它的父类
例如:
ArrayList<?>:表示可以接受任意类型
ArrayList<?> list = new ArrayList<String>();
ArrayList<?> list = new ArrayList<Integer>();
ArrayList<?> list = new ArrayList<Animal>();
ArrayList<? extends 上限>:
ArrayList<? extends Person> list = new ArrayList<Person>();
ArrayList<? extends Person> list = new ArrayList<Animal>();//Animal不行,因为Animal是父类
ArrayList<? extends Person> list = new ArrayList<Student>();
ArrayList<? extends Person> list = new ArrayList<Dog>();//Dog也不行
ArrayList<? super 下限>:
ArrayList<? super Person> list = new ArrayList<Person>();
ArrayList<? super Person> list = new ArrayList<Animal>();
ArrayList<? super Person> list = new ArrayList<Student>();//Student,因为Student是子类
ArrayList<? super Person> list = new ArrayList<Dog>();//Dog也不行
ArrayList<?>:不能添加元素,除了null
ArrayList<? extends 上限>:不能添加元素,除了null
ArrayList<? super 下限>:可以添加下限或下限子类的对象
13.5 Collections工具类
java.util.Collections:工具类,操作集合
(1)public static boolean addAll(Collection<? super T> c, T… elements)
添加elements的几个对象到c集合中。T是elements对象的类型,要求Collection集合的元素类型必须是T或T的父类
(2)public static int binarySearch(List<? extends Comparable<? super T>> list,T key)
在list集合中用二分查找key的下标,如果存在返回的是合理的下标,如果不存在返回的是一个负数下标
T是元素的类型,
<? extends Comparable<? super T>>,要求集合的元素必须实现Comparable接口 <? super T>,在实现Comparable接口,可以指定Comparable<类型实参>为T或T的父类。 (3)public static boolean disjoint(Collection<?> c1, Collection<?> c2)判断c1和c2没有交集就为true
(4)public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
求coll集合中最大元素
<T extends Object & Comparable<? super T>>:要求T或T的父类实现Comparable接口
因为找最大值需要比较大小
(5)public static <T extends Comparable<? super T>> void sort(List list) 给list集合排序
<T extends Comparable<? super T>>:要求T或T的父类实现Comparable接口
(6)public static Collection synchronizedCollection(Collection c)
以synchronizedXX开头的方法,表示把某种非线程安全集合转为一个线程安全的集合。
(7)public static List unmodifiableList(List<? extends T> list)
以unmodifiableXx开头的方法,表示返回一个“只读”的集合。
第十七章 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<T,U> | 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<T,U> | 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<T,R> | 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<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
BinaryOperator | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
ToLongBiFunction<T,U> | 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 <T> 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));
}
}
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<T,R>的抽象方法 R apply(T t)
*
* createArray这个方法中用的是Function<Integer,R[]> fun。说明T类型已经指定为Integer
* 说明
*/
// Function<Integer,String[]> 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<String> 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<Double> 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。
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) :返回一个无限流
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) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
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 实用类提供了很多静态方法,可以方便地创建常见收集器实例。
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<? 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接口指定的操作,如果不存在就不做
第十八章 设计模式
18.1 模板设计模式(了解)
1、当解决某个问题,或者完成某个功能时,主体的算法结构(步骤)是确定的,只是其中的一个或者几个小的步骤不确定,要有使用者(子类)来确定时,就可以使用模板设计模式
2、示例代码:计算任意一段代码的运行时间
//模板类
public abstract class CalTime{
public long getTime(){
//1、获取开始时间
long start = System.currentTimeMills();
//2、运行xx代码:这个是不确定的
doWork();
//3、获取结束时间
long end = System.currentTimeMills();
//4、计算时间差
return end - start;
}
protected abstract void doWork();
}
使用模板类:
public class MyCalTime extends CalTime{
protected void doWork(){
//....需要计算运行时间的代码
}
}
测试类
public class Test{
public static void main(String[] args){
MyCalTime my = new MyCalTime();
System.out.println("运行时间:" + my.getTime());
}
}
18.2 单例设计模式
单例:整个系统中,某个类型的对象只有一个。
1、饿汉式
(1)枚举式
public enum Single{
INSTANCE
}
(2)形式二
public class Single{
public static final Single INSTANCE = new Single();
private Single(){
}
}
(3)形式三
public class Single{
private static final Single INSTANCE = new Single();
private Single(){
}
public static Single getInstance(){
return INSTANCE;
}
}
2、懒汉式
(1)内部类形式
public class Single{
private Single(){}
private static class Inner{
static final Single INSTANCE = new Single();
}
public static Single getInstance(){
return Inner.INSTANCE;
}
}
(2)形式二
public class Single{
private static Single instance;
private Single(){}
public static Single getInstance(){
if(instance == null){
synchronized(Single.class){
if(instance == null){
instance = new Single();
}
}
}
return instance;
}
}
18.3 工厂设计模式
解决的问题:把对象的创建者与对象的使用者分离,把对象的创建统一到一个地方(工厂)
18.3.1 简单工厂模式
示例代码:
interface Car{
void run();
}
class BMW implements Car{
@Override
public void run() {
System.out.println("让你在宝马车里哭");
}
}
class Benz implements Car{
@Override
public void run() {
System.out.println("奔驰让你坐在引擎盖上哭");
}
}
class SimpleFactory{
public static Car getCar(String type){
switch(type){
case "宝马":
return new BMW();
case "奔驰":
return new Benz();
}
return null;
}
}
如果有反射:简单工厂模式的工厂类可以优化:
class SimpleFactoryPlus{
/*
* type:类型的全名称
*/
public static Car getCar(String type) throws Exception{
//获取Car的实现类的Class对象
Class clazz = Class.forName(type);
//创建Car的实现类的实例对象
Object obj = clazz.newInstance();
//强转类并返回车的实例对象
return (Car)obj;
}
}
18.3.2 工厂方法模式
示例代码:
interface Car{
void run();
}
class BMW implements Car{
@Override
public void run() {
System.out.println("让你在宝马车里哭");
}
}
class Benz implements Car{
@Override
public void run() {
System.out.println("奔驰让你坐在引擎盖上哭");
}
}
//工厂的公共接口
interface Factory{
Car getCar();
}
class BMWFactory implements Factory{
@Override
public BMW getCar() {
return new BMW();
}
}
class BenzFactory implements Factory{
@Override
public Benz getCar() {
return new Benz();
}
}
18.4 静态代理模式
静态代理类只能替一个主题接口进行代理工作。
如果主题接口不同,代理工作相同,也需要编写两个代理类。
示例代码:
public class TestProxy {
@Test
public void test1(){
TimeProxy tp = new TimeProxy(new GoodsDAO());
tp.add();
}
@Test
public void test2(){
TimeProxy tp = new TimeProxy(new UserDAO());
tp.add();
}
}
//主题接口
interface DAO{
void add();
}
//被代理类
class GoodsDAO implements DAO{
public void add(){
System.out.println("商品的添加");
}
}
class UserDAO implements DAO{
public void add(){
System.out.println("用户的添加");
}
}
//代理类
class TimeProxy implements DAO{
private DAO target;//target表示被代理者对象
public TimeProxy(DAO target) {
super();
this.target = target;
}
@Override
public void add() {
long start = System.currentTimeMillis();
target.add();//核心业务逻辑交还给被代理者
long end = System.currentTimeMillis();
System.out.println("时间差:" +(end-start));
}
}
18.5 动态代理
步骤:
(1)主题接口
(2)被代理类
(3)动态代理的代理工作处理器
要求必须实现:java.lang.reflect.InvocationHandler接口,重写
Object invoke(Object proxy, Method method,Object[] args)
(4)创建代理类对象
java.lang.reflect.Proxy类型的静态方法
newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )
(5)调用对用的方法
示例代码:
/* 步骤:
* 1、编写主题接口(和静态代理一样)
* 2、编写被代理类(和静态代理一样)
* 3、编写代理工作处理器:即代理类要替被代理类做什么事情
* 要求:必须实现InvocationHandler,重写
* Object invoke(Object proxy, Method method, Object[] args)
* 第一个参数:代理类对象
* 第二个参数:被代理类和代理类 要执行的方法
* 第三个参数:要执行方法的实参列表
*
* 这个invoke方法不是程序员调用,当代理类对象执行对应的代理方法时,自动调用的
*
* 4、创建代理类及其对象
* 需要:Proxy:提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
*
* static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 第一个参数:被代理类的类加载器,我们希望被代理和代理类使用同一个类加载器
* 第二个参数:被代理类实现的接口们
* 第三个参数:代理工作处理器对象
*
* 5、调用被代理的方法
*/
public class TestProxy2 {
@Test
public void test2(){
//被代理对象
YongHuDAO sd = new YongHuDAO();
ClassLoader loader = sd.getClass().getClassLoader();//被代理者的类加载器对象
Class<?>[] interfaces = sd.getClass().getInterfaces();//被代理者实现的接口们
TimeInvocationHandler h = new TimeInvocationHandler(sd);//代理工作处理器对象
//创建代理类及其对象
//proxy是代理类的对象,代理类是编译器自动编译生成的一个类
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
//这里强转的目的是为了调用增、删、改、查的方法
//为什么这里强转可以成功了,因为代理类与被代理类实现了相同的主题接口
DBDAO d = (DBDAO) proxy;
d.add();
d.update();
d.delete();
d.select();
}
@Test
public void test1(){
//被代理对象
ShangPinDAO sd = new ShangPinDAO();
ClassLoader loader = sd.getClass().getClassLoader();
Class<?>[] interfaces = sd.getClass().getInterfaces();
TimeInvocationHandler h = new TimeInvocationHandler(sd);
//创建代理类及其对象
//proxy是代理类的对象,代理类是编译器自动编译生成的一个类
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
//这里强转的目的是为了调用增、删、改、查的方法
//为什么这里强转可以成功了,因为代理类与被代理类实现了相同的主题接口
DBDAO d = (DBDAO) proxy;
d.add();
d.update();
d.delete();
d.select();
}
}
//代理工作处理器
class TimeInvocationHandler implements InvocationHandler{
private Object target;//被代理对象
public TimeInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
//被代理对象的xx方法被调用
/*
* 没有反射: 被代理对象.xx方法(args实参列表)
* 有了反射: 方法对象.invoke(被代理对象,args实参列表)
*/
Object returnValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + "方法运行时间:" + (end-start));
return returnValue;
}
}
//主题接口
interface DBDAO{
void add();
void update();
void delete();
void select();
}
//被代理类1
class ShangPinDAO implements DBDAO{
public void add(){
System.out.println("添加商品");
}
@Override
public void update() {
System.out.println("修改商品");
}
@Override
public void delete() {
System.out.println("删除商品");
}
@Override
public void select() {
System.out.println("查询商品");
}
}
//被代理类2
class YongHuDAO implements DBDAO{
public void add(){
System.out.println("添加用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void select() {
System.out.println("查询用户");
}
}