一、Stream流
(一)流式思想概述
当需要对多个元素进行操作(多步操作)时,考虑到性能及便利性,我们应该拼好一个模型再按照方案去执行它。(拼接流式模型)
Stream流是一个来自数据源的元素队列。
- 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素而是按需计算。
- 数据源流的来源,可以是集合,数组等。
Stream操作有两个基础特征
- PIpelinling:中间操作都会返回流本身。这样多个操作可以串联成一个管道,如同流式风格。
- 内部迭代:Stream提供了内部迭代方式,流可以直接调用遍历方法。
当使用流时通常包括三个基本步骤:
- 获取一个数据源(source)-> 数据转换 -> 执行操作获取想要的结果,每次转化原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
(二)两种获取流的方式
-java.util.stream.Stream<T>
是Java 8新加入的最常用的流接口。
获取流的常用方式 - 所有的Collection集合都可以通过stream默认方法读取流;
defalut Stream<E> stream( )
- Stream接口的静态方法of可以获取数组对应的流。
static <T> Stream<T> of (T..values)
- 参数是一个可变参数那么我们就可以传递一个数组
import java.util.*;
import java.util.stream.Stream;
public class Stream1 {
public static void main(String[] args) {
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stm1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stm2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取建,存储到一个Collection集合中
Set<String> keyset =map.keySet();
Stream<String> stm3 = keyset.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stm4 = values.stream();
//获取键值对(键与映射的关系)
Set<Map.Entry<String,String>> entries = map.entrySet();
Stream<Map.Entry<String,String>> streams = entries.stream();
//把数组转换为Stream流
Stream<Integer> stm6 = Stream.of(1,2,3,4,5);
//可变参数可以传递数组
Integer[] arr1={1,2,3,4,5};
Stream<Integer> stm7 = Stream.of(arr1);
String[] arr2={"a","bb","ccc"};
Stream<String> stm8 = Stream.of(arr2);
}
}
(三)常用方法
- 延迟方法:返回值类型仍然是
Stream
接口自身类型的方法,因此支持链式调用。(除终结方法以外其余均是延迟方法) - 终结方法:返回值类型不再是
Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那样的链式调用。
逐一处理:for each
-viod forEach( Consumer< ? supert T> action)
;
用来遍历流中的数据。
是一个终结方法,遍历之后不能继续调用Stream流中的其他方法。
import java.util.stream.Stream;
public class Stm2 {
public static void main(String[] args) {
//获取一个Stream流
Stream<String> stm = Stream.of("zhangsan","lisi","wangmazi");
//使用foreach对数据进行遍历
stm.forEach(name-> System.out.println(name));
}
}
filter方法
用于对Stream流中的数据进行过滤
- Stream filter(Predicate <? super T> predicate);
- filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤。
- Predicate中的抽象方法:
boolean test(T t)
import java.util.stream.Stream;
public class stm3 {
public static void main(String[] args) {
Stream<String> stm = Stream.of("zhangsan","lisi","wangmaizi","wangwu","liuliu");
Stream<String> stm1 = stm.filter((String name)->{return name.startsWith("z");});
stm1.forEach(name-> System.out.println(name));
}
}
注意:Stream流属于管道流,只能被使用一次。第一个Stream流调用完毕方法,数据就会流转到下一个Stream上。而这时第一个Stream流已经使用完毕就会关闭。所以Stream流不能再调用方法了。
映射:map
<R> Stream<R> map(Function <? suoer T,? extends R> mapper);
- 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
- Function中的抽象方法:
R apply(T t);
import java.util.stream.Stream;
public class stm4 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1","2","3","4");
Stream <Integer> stream1 = stream.map((String s)->{
return Integer.parseInt(s);
});
stream1.forEach(i->System.out.println(i));
}
}
统计:count
用于统计Stream流中元素的个数
Long count () ;
count方法不是一个终结方法,返回值是一个Long类型的整数。所以不能再继续调用Stream流中的其他方法。
import java.util.ArrayList;
import java.util.stream.Stream;
public class stm5 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++)
list.add(i);
Stream<Integer> stream = list.stream();
long count=stream.count();
System.out.println(count);
}
}
截取:limit
用于截取流中的元素。
Limit方法可以对流进行截取,只取用前n个。
-
Stream<T> limit(long maxSize)
;
参数是一个long型,如果集合长度大于参数则进行截取否则不进行操作。
limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法。
import java.util.stream.Stream;
public class stm6 {
public static void main(String[] args) {
String[] arr={"zhangsan","lisi","wangmazi","wangwu","wuliu"};
Stream<String> stream = Stream.of(arr);
Stream<String> stream1 = stream.limit(2);
stream1.forEach(name-> System.out.println(name));
}
}
跳过元素:skip
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的心流:
Stream< T > skip( long n );
如果流当前长度大于n,则跳过前n个,否则会得到一个长度为0的空流。
import java.util.stream.Stream;
public class stm7 {
public static void main(String[] args) {
String [] arr={"zhangsan","lisi","wangwu","wangmazi"};
Stream<String> stm = Stream.of(arr);
Stream<String> stm1= stm.skip(3);
stm1.forEach(name-> System.out.println(name));//wangmazi
}
}
组合:concat
- 如果有两个流,希望合并成一个流,那么可以使用
Stream
接口的静态方法concat
: staitic <T> Stream<T> concat<Stream< ? extends T> a, Stream< ? extends T> b)
import java.util.stream.Stream;
public class stm8 {
public static void main(String[] args) {
Stream<String> stm1 = Stream.of("zhangsan");
Stream<String> stm2 = Stream.of("lisi");
Stream<String> stm = Stream.concat(stm1,stm2);
stm.forEach(name-> System.out.println(name));
}
}
二、方法引用
(一)方法引用符
双冒号: :
为引用运算符,它所在的表达式为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
- Lambda表达式写法:
s-> System.out.println(s) ;
- 方法引用写法:
System.out:: :println
- 注意:Lambda中传递的参数一定是方法引用中的那个方法可以接受的类型否则会抛出异常。
(二)通过对象名引用成员方法
使用前提:对象名已经存在,成员方法也是已经存在,可以使用对象名引用成员方法。
public interface Printable {
void printString(String s);
}
public class ps {
public void Psu(String s){
System.out.println(s.toUpperCase());
}
}
public class test1 {
public static void pb(Printable p){
p.printString("hello");
}
public static void main(String[] args) {
ps p = new ps();
pb(p::Psu);
}
}
(三)通过类名称引用静态方法
通过类名引用静态方法。类已经存在,静态成员方法也已经存在,就可以通过类名直接引用静态成员方法。
public interface cal {
int calabs(int number);
}
public class calable {
public static int method(int number,cal c){
return c.calabs(number);
}
public static void main(String[] args) {
int number= method(-10,Math::abs);
System.out.println(number);
}
}
(三)通过super引用成员方法
前提是存在继承关系
public interface Greetable {
void greet();
}
//父类
public class hello {
public void sayhello(){
System.out.println("Hello!");
}
}
public class say extends hello{
@Override
public void sayhello(){
System.out.println("Hi!");
}
public void method(Greetable g){
g.greet();
}
public void show() {
method(super::sayhello);
}
public static void main(String[] args) {
new say().show();
}
}
(四)通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么就可以使用this::成员方法
的格式来使用方法引用。
public interface buyable {
void buy();
}
public class buyany {
public void buyhouse(){
System.out.println("我要买房!");
}
public void rich(buyable b){
b.buy();
}
public void happy(){
rich(this::buyhouse);
}
public static void main(String[] args) {
new buyany().happy();
}
}
(五)类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称·: : new
的格式表示。
构造方法和创建对象都已知。
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
this.name = name;
}
public Person() {
}
}
public interface personbuild {
Person build (String name);
}
public class test {
public static void printName(String name,personbuild pb){
Person person = pb.build(name);
System.out.println(person.getName());
}
public static void main(String[] args) {
printName("zhangsan",Person::new);
}
}
(六)数组的构造器引用
public interface buildarr {
int [] bulid (int length);
}
import java.util.Arrays;
public class arr {
public static int [] creat(int length,buildarr br){
return br.bulid(length);
}
public static void main(String[] args) {
/*已知创建的就是int [] 数组,数组的长度也是已知的
就可以使用方法引用。
int []引用new,根据参数传递的长度来创建数组
*/
int [] arr= creat(10,int[]::new);
System.out.println(Arrays.toString(arr));
System.out.println(arr.length);
}
}
三、Junit单元测试
(一)测试分类
- 黑盒测试:不需要写代码,给输入值,看程序能否输出期望的值
- 白盒测试:需要写代码。关注程序具体的执行流程。
(二)Junit使用:白盒测试
步骤: - 定义一个测试类(测试用例)
- 建议:
测试类名:被测试类名Test
包名:xxx.xxx.xx.test
- 定义测试方法:可以 独立运行
- 建议:
方法名:test测试方法名 testAdd( )
返回值:void
参数列表:空参
- 给方法加@Test
- 导入Junit依赖环境
绿色:测试成功 红色:测试失败
一般我们会使用断言操作来处理结果
Assert.assertEquals(期望的结果,result)
public class cal {
public int add (int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
}
import org.junit.jupiter.api.Test;
public class calable {
@Test
public void testadd(){
cal c = new cal();
int r1=c.add(10,20);
}
@Test
public void testsub(){
cal c = new cal();
int r2=c.add(10,20);
}
}
(三)@Before和@After
@Before
public void init(){
}
@After
public void close(){
System.out.println("close");
}
四、反射
(一)概述
- 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
- 反射:将类的各个组成部分封装为其他对象,这就是反射机制。
- 三个阶 段:Soucre源代码阶段—Class类对象阶段—Runtime运行时阶段
- 好处:
- 可以在程序运行过程中,操作这些对象
- 可以解耦,提高程序的可扩展性
- 获取Class对象的三种方式
- 源代码:
Class.foName(“全类名”)
:将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类。 - .Class对象阶段:通过类名的属性Class获取。多用于参数的传递。
- 对象.getClass( ) : getClass( )方法在Object类中定义着。多用于获取字节码的方式。
public class test {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName("全类名")
Class cls1 = Class.forName("Demo7.Person");
System.out.println(cls1);
//2.类名.class
Class cls2 = Person.class;
System.out.println(cls2);
//3.对象.getClass
Person p = new Person();
Class cls3 = p.getClass();
System.out.println(cls3);
}
}
- 结论:同一个字节码文件(.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个。
(二)Class对象功能概述
- 获取成员变量
- 获取构造方法
- 获取成员方法
- 获取类名
获取成员变量
-
Filed [ ] getFields( )
:获取所有public修饰的成员变量 -
Dield getField(String name)
:获取指定名称的成员变量 -
Field [ ] getDeclaredFields( )
: 获取所有的成员变量,不考虑修饰符 Field getDeclaredField(String name)
操作:- 设置值:
void set(Object obj, Object value)
- 获取值:
get( Object obj)
- 忽略访问权限修饰符的安全检查:
setAccessible(true)
:暴力反射
public class test {
public static void main(String[] args) throws Exception {
Field[] fields = Person.class.getFields();
for(Field f:fields) {
System.out.println(f);
}
Field a = Person.class.getField("a");
//获取成员变量a的值
Person p = new Person();
Object value = a.get(p);
System.out.println(value);
a.set(p,"zhangsan");
System.out.println(p);
Field[] dec = Person.class.getDeclaredFields();
for (Field f:dec){
System.out.println(f);
}
Field d = Person.class.getDeclaredField("a");
d.setAccessible(true);//暴力反射
Object value2 = d.get(p);
System.out.println(value2);
}
}
public java.lang.String Demo7.Person.a
null
Person{name='null', age=0}
private java.lang.String Demo7.Person.name
private int Demo7.Person.age
public java.lang.String Demo7.Person.a
zhangsan
获取构造方法
Constructor<?> [ ] getConstructors ( )
Constructor<T> getConstructtor( 类<?> ... parameterTypes)
Constructor<T> getDeclaredConstructor( 类<?> ... parameterTypes)
Constructor<?> [ ] getDeclaredConstructors( )
- 创建对象:
T newInstance(Object... initargs)
空参数构造方法,操作可以简化:Class对象的newInstance
方法
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) throws Exception {
Constructor constructor = Person.class.getConstructor(String.class,int.class);
System.out.println(constructor);
//创建对象
Object person = constructor.newInstance("zhangsan",18);
System.out.println(person);
Constructor constructor1 = Person.class.getConstructor();
System.out.println(constructor1);
Object person1 = constructor1.newInstance();
System.out.println(person1);
Object o =Person.class.newInstance();
System.out.println(o);
}
}
public Demo7.Person(java.lang.String,int)
Person{name='zhangsan', age=18}
public Demo7.Person()
Person{name='null', age=0}
Person{name='null', age=0}
获取成员方法
Method getMethod(String name, 类<?>... parameterTypes)
-
Method [ ] getDeclaredMethods( )
:获取所有public的修饰方法 Methdod getDeclaredMethod(String name,类<?> ... prameterTypes)
- 方法对象
执行方法:Object invoke( Object obj,Object... args)
获取方法的名称:String getName
:获取方法名
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Person p =new Person();
Method eat_method2 = Person.class.getMethod("eat", String.class);
eat_method2.invoke(p,"饭");
Method[] methods = Person.class.getMethods();
for(Method method:methods){
System.out.println(method);
String name = method.getName();
System.out.println(name);
}
//获取类名
String className = Person.class.getName();
System.out.println(className);
}
}
案例
- 需求:写一个框架,可以帮我们创建任意类的对象,并且执行其中任意的方法。
- 实现:
- 配置文件
- 反射
- 步骤:
- 将需要创建对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类
- 创建对象
- 执行方法
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class reflect {
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
ClassLoader classLoader = reflect.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("Demo7\\pro.person");
pro.load(is);
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
Class cls = Class.forName(className);
Object obj = cls.newInstance();
Method method = cls.getMethod(className);
method.invoke(obj);
}
}
注解
概念:说明程序的。给计算机看的。
注释:用文字描述程序的,给程序员看的。
- 定义:从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
- 作用分类:
- 编写文档:通过代码里标识的注解生成文档【生成doc文档】
- 代码分析:通过代码里标识的注解对代码进行分析
- 通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
- JDK中预定义的一些注解
@Override:检测被该注解标注的方法是否是继承自父类(接口)的。
@Deprecated:该注解标注的内容,表示已过时。
@SuppressWarnings:压制警告,一般写在类前面。@SuppressWarnings(“all")
自定义注解
格式 :
元注解
-public @interface 注解名称{ }
注解:本质上是一个接口,该接口默认继承Annotation。
属性:接口中的抽象方法 - 要求:
- 属性的返回值类型
- 基本数据类型
- String、枚举、注解
- 以上类型的数组
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,使用{}包裹,如果数组中只有一个值,则{}可以省略。
元注解
用于描述注解的注解。
-
@Target
:描述注解能够作用的位置
ElementType取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上 -
@Retention
:描述注解被保留的阶段 -
@Documented
:描述注解是否被抽取到api文档中 -
@Inherited
:藐视注解是否被子类继承
import java.lang.annotation.*;
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited()
public @interface anno {
}
解析注解
- 获取注解中定义的属性值
import java.lang.annotation.*;
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface anno {
String className();
String methodName();
}
@anno(className = "Demo7.Person",methodName = "eat")
public class test {
public static void main(String[] args) {
//1.解析注释
//1.1获取该类的字节码文件对象
Class<test> testClass = test.class;
//2.获取上边的注解对象:实际就是在内存中生成了一个该注解接口的子类实现对象
anno anno = testClass.getAnnotation(Demo8.anno.class);
//调用注解对象中定义的抽象方法,获取返回值
String className = anno.className();
System.out.println(className);
}
}