属性集, 函数式编程, stream流
I/O流小结
文件复制:
BufferedInputStream/BufferedOutputStream
文件内容读写:
BufferedReader/PrintWriter
FileReader/FileWriter
对象读写:
ObjectInputStream/ObjectOutputStream
1. 属性集
Properties, 仅支持String类型的属性映射
extends Hashtable implements Map
key - value,
推荐使用的方法
void setProperty(String key, String value)
String getProperty(String key)
加载属性集:
void load(Reader)
void load(InputStream)
代码实现:
public class PropertiesTest {
@Test
//属性集创建
//属性列表为HashTable的子类
// Properties类表示一组持久的属性。 Properties可以保存到流中或从流中加载。属性列表中的每个键及其对应的值都是一个字符串
public void propertiesDefine(){
//构造一个新的属性集
Properties properties = new Properties();
//不要使用get/put方法因为可以输入Object对象
//添加键值对
//键值一般用英文
properties.setProperty("价格", "278");//参数只支持String类型
properties.setProperty("评分", "特别好评");
System.out.println(properties);
//获取键对应的值
System.out.println( properties.get("价格"));
System.out.println( properties.get("评分"));
//获取键值列表
Set<String> key = properties.stringPropertyNames();
//键值遍历
for(String k: key) {
System.out.println(k + "=" + properties.get(k));
}
}
@Test
public void read(){
//构造一个新的属性集
Properties properties = new Properties();
//通过流加载属性集
//在与类的同一个包中找目标文件
String path = PropertiesTest.class.getResource("config.properties").getPath();
System.out.println(path);
try(
FileInputStream fis = new FileInputStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader(fis, "gbk"))
){//load(InputStream/Reader)
properties.load(fis);//从输入字节流读取属性列表(键和元素对)。
properties.load(br);//以简单的线性格式从输入字符流读取属性列表(关键字和元素对)。
//由于我的文件为gbk编码的,所以默认的ISO8859-1解码方式会生成乱码
//如果用中文
System.out.println(properties.getProperty("name"));
//解决乱码
String name = new String(properties.getProperty("name").getBytes("ISO8859-1"), "gbk");
System.out.println(name);
}catch (IOException e){
e.printStackTrace();
}
}
@Test
public void write(){
//构造一个新的属性集
Properties properties = new Properties();
//键值一般用英文
properties.setProperty("name", "怪物猎人世界");
properties.setProperty("price", "278");//参数只支持String类型
properties.setProperty("review", "特别好评");
//通过流打印属性集
//在与类的同一个包中找目标文件
String path = PropertiesTest.class.getResource("config.properties").getPath();
System.out.println(path);
try(
PrintWriter pw = new PrintWriter(path, "gbk")
){//list(PrintWriter/PrintStream)
properties.list(pw);//将此属性列表打印到指定的输出流。
}catch (IOException e){
e.printStackTrace();
}
}
}
2. 函数式编程
JDK 8 特性
函数式编程: Lambda表达式(函数式接口作为方法的参数)
函数式接口: 接口中只有一个抽象方法
常用函数式接口: Supplier Consumer Predicate Function
Supplier: 生产者 - T get();
import java.util.Scanner;
import java.util.function.Supplier;
public class SupplierTest {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
//Supplier是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象。
String s = change(new Supplier<String>(){
@Override
public String get() {
return console.nextLine();
}
});
System.out.println(s);
//lambda简化
s = change(() -> console.nextLine());
System.out.println(s);
}
public static String change(Supplier<String> s){
//生产者的意义就是生产对象
//使用get方法生产对象,返回值为Supplier的泛型
return s.get();
};
}
Consumer: 消费者 - void accept(T t); 使用这个对象
默认方法 - andThen(Consumer)
将两个消费方式组合在一块
import java.util.Scanner;
import java.util.function.Consumer;
public class ConsumerTest {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
String s = console.nextLine();
toCase(s, new Consumer<String>() {
@Override
//消费一个参数
public void accept(String s) {
System.out.println(s.toLowerCase());//全部小写
}
}, new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s.toUpperCase());//全部大写
}
});
//lambda简化
toCase(s, (s1) -> System.out.println(s.toLowerCase()), (s2) -> {
System.out.println(s.toUpperCase());
});
}
//消费一个数据,其数据类型由泛型决定, 返回void。
public static void toCase(String s, Consumer<String> c, Consumer<String> a){
//抽象方法accept,返回值为void
c.accept(s);
a.accept(s);
//可以将上面两步使用默认方法andThen连接,效果相同,返回值为void
c.andThen(a).accept(s);
}
}
Predicate: 对对象做判断 - boolean test(T t);
默认方法 - or(||) and(&&) negate(!)
import java.util.ArrayList;
import java.util.function.Predicate;
public class PredicateTest {
//predicate接口:进行数据的判断
//1.接口抽象方法 boolean test(T t) 。用于条件判断的场景:
public static boolean judge(String s,Predicate<String> one){
return one.test(s);
}
//2.接口默认方法 and(predicate) 将多次test方法的结果相与,返回boolean结果
public static boolean judgeAnd(String s,Predicate<String> one, Predicate<String> two){
return one.and(two).test(s);
//等同于:one.test(s) && two.test(s)
}
//2.接口默认方法 or(predicate) 将多次test方法的结果相与,返回boolean结果
public static boolean judgeOr(String s,Predicate<String> one, Predicate<String> two){
return one.or(two).test(s);
//等同于:one.test(s) || two.test(s)
}
//3.默认接口方法 negate() 取反
public static boolean judgeNegate(String s,Predicate<String> one){
return one.negate().test(s);
//等同于:!one.test(s)
}
public static void main(String[] args) {
//案例:数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中,需要同时满足两个条件:
//1. 必须为pc平台;
//2. 名字为4个字。
String[] array = new String[]{"怪物猎人,pc,278", "塞尔达传说,switch,315", "黎明杀机,pc,67", "女神异闻录5,ps4,199",
"光环,Xbox,250"};
//创建一个ArrayList表
ArrayList<String> list = new ArrayList<>();
//使用Predicate接口的and方法满足上述条件
for(String s : array){
//将信息分离
String[] info = s.split(",");
//直接使用lambda简化函数式接口参数
boolean b = judgeAnd(s,s1 -> info[1].equals("pc"),//1. 必须为pc平台;
s2 -> info[0].length() == 4); //2. 名字为4个字。
if(b){//如果满足条件就添加进列表
list.add(s);
}
}
System.out.println(list);
list.clear();
//案例:只要价格低于100 或者 平台不是pc的就加入列表
//使用Predicate接口的or和negate方法满足上述条件
for(String s : array){
//将信息分离
String[] info = s.split(",");
//直接使用lambda简化函数式接口参数
boolean b = judgeOr(s,s1 -> Integer.parseInt(info[2]) < 100,//1. 价格低于100
s2 -> judgeNegate(info[1], (s3) -> s3.equals("pc"))); //2. 平台不是pc
if(b){//如果满足条件就添加进列表
list.add(s);
}
}
System.out.println(list);
}
}
Function<T, R>: 类型转换 - R apply(T t);
默认方法 - andThen(Function)
连续做两种类型转换
import java.util.function.Function;
public class FunctionTest {
//Function<T, R>接口用来根据一个类型的数据得到另一个类型的数据 T:输入类型 R:结果类型
//apply方法用于类型转换
public static Integer FunctionApply(String s, Function<String, Integer> one){
return one.apply(s);
}
//andThen(after)方法返回一个组合函数,首先将此函数应用于其输入,然后将after函数应用于结果。
public static String FunctionAndThen(String s, Function<String, Integer> one, Function<Integer, Integer> two, Function<Integer, String> three){
return one.andThen(two).andThen(three).apply(s);
//相当于:three.apply(two.apply(one.apply(s)));
}
public static void main(String[] args) {
String s = "怪物猎人";
String change = FunctionAndThen(s, s1 -> s1.length(), i -> i * 100, i1 -> "价格为" + i1 + "元");
System.out.println(change);
}
}
3. stream流
当需要对多个元素进⾏操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该⾸先拼好⼀个“模型”步骤⽅案,然后再按照⽅案去执⾏它。
这张图中展示了过滤、映射、跳过、计数等多步操作,这是⼀种集合元素的处理⽅案,⽽⽅案就是⼀种“函数模型”。图中的每⼀个⽅框都是⼀个“流”,调⽤指定的⽅法,可以从⼀个流模型转换为另⼀个流模型。⽽最右侧的数字3是最终结果。
这⾥的 filter 、 map 、 skip 都是在对函数模型进⾏操作,集合元素并没有真正被处理。只有当终结⽅法 count 执⾏的时候,整个模型才会按照指定策略执⾏操作。⽽这得益于Lambda的延迟执⾏特性。
备注:“Stream流”其实是⼀个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
Stream流: 操作 数组或者集合
获取流: 1.集合 Collection Map 2.数组
import java.util.*;
import java.util.stream.Stream;
public class GetStream {
//获取流.stream()方法
public static void main(String[] args) {
//1.单列集合获取流
List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
//2.多列集合获取流(使用方法获取单列集合,在通过单列集合获取流)
Map<String, Integer> map = new HashMap<>();
//获取键值流
Set<String> keySet = map.keySet();
Stream<String> keyStream = keySet.stream();
//获取键值对流
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Stream<Map.Entry<String, Integer>> entryStream = entrySet.stream();
//获取values流
Collection<Integer> values = map.values();
Stream<Integer> valueStream = values.stream();
//3.数组获取流(Stream<T>接口的静态方法 of())
Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
Stream<Integer> arrStream = Stream.of(arr);
//of方法重载,数组可用变长数组代替
Stream<Integer> arrStream1 = Stream.of(1, 2, 3, 4, 5);
}
}
常用API:
void forEach(Consumer) - 终结方法
Stream filter(Predicate) - 延迟方法
Predicate中test返回true, 是保留在流中的
Stream map(Function<T, R>) - 延迟方法
将 流中的 T类型的数据, 转成 R类型数据, 并且存入新的流中
static Stream concat(Stream, Stream)
将两个流拼接成一个
代码实现:
import org.junit.Test;
import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class StreamAPI {
//创建列表
private static ArrayList<String> list;
//给列表添加元素
public void setList(){
list = new ArrayList<>();
list.add("黎明杀机");
list.add("怪物猎人世界");
list.add("饥荒");
list.add("杀戮尖塔");
list.add("星露谷物语");
list.add("赛博朋克2077");
list.add("神界:原罪2");
list.add("文明6");
}
@Test//单元测试时并没有加载类,所以也没有运行静态方法
public void forEachDemo(){
setList();
//forEach(消费者函数接口对象), 终结方法, 遍历消费
list.stream().forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
System.out.println(list);
}
@Test
public void filterDemo(){
setList();
//filter(判断接口对象), 延迟方法, 通过实现判断接口过滤流
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() == 2;
}
}).forEach(s -> System.out.println(s));
//流并不是对原集合进行操作,只是在改变基于原集合的函数模型
System.out.println(list);
}
@Test
public void mapDemo(){
setList();
//map(函数接口对象), 延迟方法, 通过实现函数接口将流中的元素映射到另一个流
Stream<Integer> length = list.stream().map(new Function<String, Integer>(){
@Override
public Integer apply(String s) {
return s.length();
}
});
length.forEach(s -> System.out.println(s));
}
@Test
public void countDemo(){
setList();
//count(), 终结方法, 返回元素个数,返回值为long类型
long size = list.stream().count();
System.out.println(size);
}
@Test
public void limitDemo(){
setList();
//limit ⽅方法可以对流进行截取,只截取前n个。
//limit(long size), 延迟方法, 参数是long类型,如果集合当前长度大于参数则进行截取;否则不进行操作。
list.stream().limit(3).forEach(s -> System.out.println(s));
}
@Test
public void skipDemo(){
setList();
//skip方法可以跳过前几个元素。
//skip(long size), 延迟方法, 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
list.stream().skip(3).forEach(s -> System.out.println(s));
}
@Test
public void concatDemo(){
setList();
//concat方法可以组合两个流。
//Stream.concat(Stream<T>, Stream<N>), 延迟方法, 将两个流合并为一个流
Stream.concat(list.stream(), list.stream()).forEach(s -> System.out.println(s));
}
}
案例
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
public class Example {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("黎明杀机");
set.add("怪物猎人世界");
set.add("饥荒");
set.add("杀戮尖塔");
set.add("星露谷物语");
set.add("赛博朋克2077");
set.add("神界:原罪2");
set.add("文明6");
// System.out.println("文明6".matches(".*\\d.*"));
// System.out.println("饥荒".matches("^[\\u4E00-\\u9FA5]+$"));
// 1. 第一个队伍只要名字全是中文游戏;存储到一个新集合中。
// 2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
Stream<String> queue1 = set.stream().filter(name -> name.matches("^[\\u4E00-\\u9FA5]+$")).limit(3);
// 3. 第二个队伍只要名字含有数字的游戏;存储到一个新集合中。
// 4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
Stream<String> queue2 = set.stream().filter(name -> name.matches(".*\\d.*")).skip(2);
// 5. 将两个队伍合并为一个队伍;存储到一个新集合中。
Stream<String> appendQueue = Stream.concat(queue1, queue2);
// 6. 根据名字创建Game对象;映射到一个新流中。
Stream<Game> game = appendQueue.map(name -> new Game(name));
// 7. 打印整个队伍的Game对象信息。
game.forEach(g -> System.out.println(g.getName()));
}
}