《Java 8 函数式编程》的笔记
简单mark下里面的代码
习题解:https://github.com/RichardWarburton/java-8-lambdas-exercises
2.Lamba表达式
相当于匿名方法,代码即数据,闭包且适用于函数接口。
Lamba可应用在
匿名内部类 button.addActionListenr( event -> System.out.println("button clicked") )
javac(编译器)会根据程序上下文推断出参数类型(有时不一定),类型推断始于Java7,`new HashMap<>
中的<>就会推断泛型参数的类型
局部变量BinaryOperator<Long> add = (x, y) -> x+y;
,这样add相当于x+y
这行代码,而目标类型为Long
(上下文环境类型,就像数组对象和null赋值后才知道类型)。
引用所在方法变量时,声明为final,实际引用的是该值非变量。如
final String name = getUserName();
button.addActionListener( event -> System.out.println("hi"+name) );
这时final在Java8中可省。
函数接口有
Predicate<T> test
Consumer<T> accept
Function<T,R> apply
Supplier<T> get
UnaryOperator<T>
BinaryOperator<T>
3.Stream
Stream为内部迭代(集合类中实现,无需在程序中编写),如stream()返回对象,而非iterator()返回用于外部迭代的Iterator对象
int count = 0;
for (Artist artist : allArtists)
if (artist.isFrom("London"))
count++;
//前者为封装了迭代的语法糖
int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while (iterator.hasNext()){
Artist artist = iterator.next();
if (artist.isFrom("London"))
count++;
}
链式调用由多个Lazy操作组成,以Eager操作结束,只迭代一次。
allArtists.stream()
.filter( artist -> artist.isFrom("London") )
.count();
常用的操作
//collect
List<String> collected = Stream.of("a", "b", "c")
.collect( Collectors.toList() );
assertEquals( Arrays.asList("a", "b", "c"), collected );
//map( Function<T, R> )
List<String> collected = Stream.of("a", "b", "c")
.map( string -> string.toUpperCase() )
.collect( Collectors.toList() );
//filter( Predicate<T, boolean> )
List<String> collected = Stream.of("a", "bb", "c")
.filter( string -> string.length >1 )
.collect( Collectors.toList() );
//flatMap( Function<T, Stream> )
List<Integer> together = Stream.of( Arrays.asList(1, 2), Arrays.asList(1, 2) )
.flatMap(numbers -> numbers.stream())
.collect( Collectors.toList() );
artists.stream()
.flatMap(artist -> Stream.of(artist.getName(), artist.getNationality()))
.collect(toList());
//max.min
Artist artist = allArtist.stream()
.min( Comparator.comparing(artist -> artist.getName().length ) )
.get();
reduce,从一组值生成一个值的操作(如count,min,max)。
int count = Stream.of(1, 2, 3)
.reduce(0, (acc, element) -> acc + element );
//展开后
BinaryOperator<Integer> accumulator = (acc, element) -> acc + element;
int count = accumulator.apply(
accumulator.apply(
accumulator.apply(0, 1),
2),
3);
//重构前
for (Album album : albums){
for (Track track : album.getTrackList()){
if (track.getLength() > 60){
String name = track.getName();
trackNames.add(name);
}
}
}
//重构后
albums.stream()
.flatMap( album -> album.getTracks() )
.filter( track -> track.getLength() > 60)
.map( track -> track.getName() )
.collect( toSet() )
尽量避免副作用,即不改变程序或外界的状态。
4.类库
Java泛型是基于对泛型参数类型的擦除(Object实例),List实际上得到的List,整型元素(4B)变为指向整型对象(16B)内存的指针。
Stream对此区分(int,long,double),减少性能开销。
public static void printTrackLengthStatistics(Album album){
IntSummaryStatistics trackLengthStats
= album.getTracks()
.mapToInt(track -> track.getLength()) //返回IntStream
.summaryStatitics();
System.out.println("Max: %d, Min: %d, Ave: %d, Sum: %d",
trackLengthStats.getMax(),
trackLengthStats.getMin(),
trackLengthStats.getAverage(),
trackLengthStats.getSum())
}
重载解析时,lamba作为参数时,类型由目标类型推到得出。从函数接口参数或最具体的类型推导出。
函数接口需要添加@FunctionalInterface
为了二进制接口的兼容性(Collection接口添加了stream方法),Java8添加默认方法。
//Iterable
default void foreach(Consumer< ? super T> action){
for (T t : this){
action.accept(t);
}
}
与静态方法相反,与类定义的方法产生冲突时,优先选择类的具体方法。
因此产生的多重继承(避免对象状态的继承),需要在子类中Override
public class MusicalCarriage implements Carriage, Jukebox{
@Override
public String rock(){
return Carriage.super.rock();
}
}
同时添加了接口的静态方法特性,如Stream.of。
为了避免引用null值的变量,可以使用Optional。
Optional<Stirng> a = Optional.of("a");
Optional emptyOptional = Optional.empty();
Optional emptyOptional = Optional.ofNullable(null);
assertEquals(a.isPresent());
assertEquals("b", emptyOptional.ofElse("b"));
assertEquals("c", emptyOptional.ofElseGet( ()->"c" ));
5.高级集合类和收集器
方法引用,即ClassName::methodName,如Artist::new、String[]::new。
流中元素的出现顺序与创建的集合有关,有序较友好,按序处理用forEachOrdered。
转换为其他集合、值
stream.collect(toCollection(TreeSet::new));
//找出最大
public Optional<Artist> biggestGroup(Stream<Artist> artists){
Function<Artist, Long> getCount = artist -> artist.getMemebers().count();
artists.collect(maxBy(comparing(getCount)));
}
//找出平均数
public double averageNumberOfTracks(List<Album> albums){
return albums.stream().
.collect(averageingInt(album -> album.getTrackList().sizeA() ));
}
数据分块
public Map<Boolean, List<Artist>> bandAndSolo(Stream<Artist> artists){
return artists.collect(partitioningBy(artist -> artist.isSolo()));
}
数据分组
public Map<Boolean, List<Artist>> bandAndSolo(Stream<Artist> artists){
return artists.collect(groupingBy(album -> album.getMainMusician()));
}
字符串
StringBuilder builder = new StringBuilder("[");
for(Artist artist : artists){
if (builder.length()>1)
builder.append(", ");
String name = artist.getName();
builder.append(name);
}
builder.append("]");
String result = builder.toString();
//Stream
String result = artists.stream()
.map( artist -> artist.geName() )
.collect( Collectors.joining(", ", "[", "]") );
组合收集器
Map<Artist, Long> a = albums.collect(groupingBy(album -> album.getMainMusician(), counting()));
Map<Artist, List<String>> b = albums.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
重构收集器
StringCombiner combined = artists.stream()
.map(Artist::getName)
.reduce(new StringCombiner(", ", "[", "]"),
StringCombiner::add, StringCombiner::merage);
String result = combined.toString();
//定制化
StringCombiner combined = artists.stream()
.map(Artist::getName)
.collect(Collectors.reducing(new StringCombiner(", ", "[", "]"),
name -> new StringCombiner(", ", "[", "]").add(name), StringCombiner::merge));
一些细节
//读缓存
public Artist getArtist(String name){
Artist artist = artistCache.get(name);
if(artist == null){
artist = readArtistFromDB(name);
artistCache.put(name);
}
return artist;
}
//Stream
public Artist getArtist(String name){
return artistCache.computeIfAbsent(name, this::readArtistFromDB);
}
//在Map上迭代
Map<Artist, Integer> countOfAlbums = new HashMap<>();
for(Map.Entry<Artist, List<Album>> entry:albumsByArtist.entrySet()){
Artist artist = entry.getKey();
List<Album> albums = entry.getValue();
aountOfAlbums.put(artist, albums.size());
}
//Stream
Map<Artist, Integer> countOfAlbums = new HashMap<>();
albumsByArtist.forEach( (artist, albums) -> {aountOfAlbums.put(artist, albums.size());} );
6.数据并行化
并发为共享时间,并行为同时发生,有数据并行化和任务并行化,利用多核。
parallel和parallelStream
//蒙特卡洛模拟法并行化模拟掷骰子事件
public Map<Integer, Double> parallelDiceRolls() {
double fraction = 1.0/N;
return IntStream.range(0, N)
.parallel()
.mapToObj(twoDiceThrows())
.collect(groupingBy(side -> side, summingDouble(n -> fraction)));
}
//手动模拟
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
public class ManualDiceRolls {
private static final int N = 1000;
private final double fraction;
private final Map<Integer, Double> results;
private final int numberOfThreads;
private final ExecutorService executor;
private final int workPerThread;
public static void main(String[] args){
ManualDiceRolls roles = new ManualDiceRolls();
roles.simulateDiceRoles();
}
public ManualDiceRolls(){
fraction = 1.0/N;
results = new ConcurrentHashMap<>();
numberOfThreads = Runtime.getRuntime().availableProcessors();
executor = Executors.newFixedThreadPool(numberOfThreads);
workPerThread = N / numberOfThreads;
}
public void simulateDiceRoles(){
List<Future<?>> futures = submitJobs();
awaitCompletion(futures);
printResults();
}
private void printResults(){
results.entrySet().forEach(System.out::println);
}
private List<Future<?>> submitJobs(){
List<Future<?>> futures = new ArrayList<>();
for ( int i=0; i<numberOfThreads; i++){
futures.add(executor.submit(makeJob()));
}
return futures;
}
private Runnable makeJob(){
return () -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
for ( int i=0; i<workPerThread; i++){
int entry = twoDiceThrows(random);
accumlateResult(entry);
}
};
}
private void accumlateResult(int entry){
results.compute( entry,
(key, previous) -> previous==null ?fraction :previous+fraction
);
}
private int twoDiceThrows(ThreadLocalRandom random){
int firstThrow = random.nextInt(1, 7);
int secondThrow = random.nextInt(1, 7);
return firstThrow+secondThrow;
}
private void awaitCompletion(List<Future<?>> futures){
futures.forEach( (future) -> {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
);
executor.shutdown();
}
}
限制
reduce操作在并行时,初值为组合函数的恒等值,必须符合结合律。
性能
主要由数据大小,源数据结构,装箱,核的数量,单元处理开销。
按源数据结构(分解方式:对半),性能好:ArrrayList、IntStream.range等;性能一般:HashSet、TreeSet等;性能差:LinkedList、Streams.iterate、BufferedRead.lines。
无状态并行操作,性能更好,如map、filter、faltMap,反之sorted、distinct、limit。
private int addIntegers(List<Integer> values){
return values.parallelStream()
.mapToInt( i -> i )
.sum();
}
并行化数组操作
//for循环
public static double[] imperativeInitilize(int size){
double[] values = new double[size];
for (int i=0; i<values.length; i++){
values[i] = i;
}
return values;
}
//并行化初始
public static double[] parallelInitialize(int size){
double[] values = new double[size];
Arrays.parallelSetAll(values, i->i);
return values;
}
//对时间序列计算简单滑动平均数
public static double[] simpleMovingAverage(double[] values, int n){
double[] sums = Arrays.copyOf(values, values.length);
Arrays.parallelPrefix(sums, Double::sum);
int start = n-1;
return IntStream.range(start, sums.length)
.mapToDouble(i -> {
double prefix = i==start ? 0: sums[i-n];
return (sums[i]-prefix)/n;
})
.toArray();
}
7.测试、调试、重构
//if(logger.isDebugEnabled())
Logger logger = new Loggger();
logger.debug(()->"Look at"+expensiveOperation());
//匿名类
ThreadLocal<Album> thisAlbum = ThreadLocal.withInitial( ()->database.lookupCurrentAlbum() );
//Write Everything Twice
public long countFeature(ToLongFunction<Album> function){
return albums.stream()
.mapToLong(function)
.sum();
}
public logn countTracks(){
return countFeature( album -> album.getTracks().count());
}
public long countRunningTime(){
return countFeature(album -> album.getTracks()
.mapToLong( track -> track.getLength() )
.sum());
}
单元测试
使用方法引用
public static List<String> elementFirstToUppercase(List<String> words){
return words.stream()
.map(Testing::firstToUppercase)
.collect(Collectors.<String>toList());
}
public static String firstToUppercase(Stirng value){
char firstChar = Character.toUpperCase(value.charAt(0));
return firstChar+value.substring(1);
}
@Test
public void twoLetterStringConvertedToUpperCase(){
String input = "ab";
String result = Testing.firstToUpperCase(input);
assertEquals("Ab", result);
}
@Test
public void canCountFeatures(){
OrderDomain order = new OrderDomain( asList(
newAlbum("A"),newAlbum("B"),newAlbum("C"),newAlbum("D")
) );
assertEquals(8, order.countFeature(album->2));
}
//结合Mockito框架
List<String> list = mock(List.class);
when (list.size()).thenAnswer(inv->otherList.size());
assertEquals(3, list.size());
使用peek,记录中间值。查看流中的每个值并操作,调试
Set<String> nationalities = album.getMusicians()
.filter(artist -> artist.getName().startsWith("The"))
.map(artist -> artist.getNationality())
.peek(nation -> System.out.println("Nation:"+nation))
.collect(Collectors.<String>toSet());
8.设计和架构的原则
lambda改变设计模式
命令行模式
public interface Editor{
public void open();
public void save();
public void close();
}
public interface Action{
public void perform();
}
public class Open implements Action{
private final Editor editor;
public Open(Editor editor){
this.editor = editor;
}
@Override
public void perform(){
editor.open();
}
}
publvi class Macro {
private final List<Action> actions;
public Macro(){
actions = new ArrayList<Action>;
}
public void record(Action action){
actions.add(action);
}
public void run(){
acitons.forEach(Action::perform);
}
}
Macro macro = new Macro();
macro.record(new Open(editor));
macro.record(new Save(editor));
macro.record(new Close(editor));
marco.run();
//lambda
Macro macro = new Macro();
macro.record(editor::open);
macro.record(editor::save);
macro.record(editor::close);
marco.run();
策略模式
public class Compressor{
private final CompressionStrategy strategy;
public Compressor(CompressionStrategy strategy){
this.strategy = strategy;
}
public void compress(Path inFile, File outFile) throws IOException {
try(OutputStream outStream = new FileOutputStream(outFile)){
File.copy(inFile, strategy.compress(outStream));
}
}
}
public class GzipCompressionStrategy implements CompressionStrategy{
@Override
public OutputStream compress(OutputStream data) throws IOException{
return new GZIPOutputStream(data);
}
}
Compress gzipCompressor = new Compress(new GzipCompressionStrategy);
gzipCompressor.compress(inFile, outFile);
Compress zipCompressor = new Compress(new ZipCompressionStrategy);
zipCompressor.compress(inFile, outFile);
//lambda
Compress gzipCompressor = new Compress(GZIPOutputStream::new);
gzipCompressor.compress(inFile, outFile);
Compress zipCompressor = new Compress(ZIPOutputStream::new);
zipCompressor.compress(inFile, outFile);
观察者模式
public class Nasa implements LandingObserver{
@Override
public void observeLanding(String name){
if(name.contains("Apollo")){
System.out.println("We made it~");
}
}
}
public class Moon{
private final List<LandingObserver> observers = new ArrayList<>();
public void land(String name){
for (LandingObserver observer: observers){
observer.observeLanding(name);
}
}
public void startSpying(LandingObserver observer) {
observers.add(observer);
}
}
Moon moon = new Moon();
moon.startSpying(new Nasa());
moon.startSpying(new Aliens());
moon.land("An asteroid");moon.land("Apollo 11");
//lambda
Moon moon = new Moon();
moon.startSpying(name -> {
if(name.contains("Apollo")){
System.out.println("We made it~");
}
});
moon.startSpying(name -> {
if(name.contains("Apollo")){
System.out.println("They're distracted.");
}
});
moon.startSpying(new Aliens());
moon.land("An asteroid");moon.land("Apollo 11");
模板方法模式
public abstract class LoanApplication{
public void checkLoanApplication() throws ApplicationDenied{
checkIdentity();
checkCreditHistory();
checkIncomeHistroy();
reportFindings();
}
public void checkLoanApplication() throws ApplicationDenied;
public void checkCreditHistory() throws ApplicationDenied;
public void checkIncomeHistroy() throws ApplicationDenied;
private void reportFindings(){ ... }
}
//lamba
public class LoanApplication{
private final Criteria identity;
private final Criteria creditHistory;
private final Criteria incomeHistroy;
public void LoanApplication(Criteria identity,Criteria creditHistory,Criteria incomeHistroy){
this.identity = identity;
this.creditHistory = creditHistory;
this.incomeHistroy = incomeHistroy;
}
public void checkLoanApplication() throws ApplicationDenied{
identity.check();
creditHistory.check();
incomeHistroy.check();
reportFindings();
}
private void reportFindings(){ ... }
}
public interface Criteria{
publci void check() throws ApplicationDenied;
}
public CompanyLoanApplication extends LoanApplication{
public CompanyLoanApplication(Company company){
super(company::checkHistory,company::checkHistoricalDebt,company::checkProfitAndLoss);
}
}
使用lambda表达式的DSL
流畅性依赖于善用IDE的自动补全
public static void describe(String name, Suite behavior){
Description description = new Description(name);
behavior.specifySuite(description);
}
public void should(String description, Specification specification){
try{
Except except = new Except();
specification.specifyBehaviour(except);
Runner.current.recordSuccess(suite, description);
}catch(AssertionError cause){
Runner.current.recordFailure(suite, description, cause);
}catch(Throwable cause){
Runner.current.recordError(suite, description, cause);
}
}
//except.that(stack.pop()).isEqualTo(2);
public final class Except{
public BoundException that(Object value){
return new BoundException(value);
}
}
public class StackSepc{{
...
}}
//==
public class StackSepc{
public StackSepc(){
...
}
}
使用lambda表达式的SOLID
单一功能原则
public long countPrimes(int upTo){
long tally = 0;
for (int i=0; i<upTo; i++){
boolean isPrime = true;
for (int j=2; j<i; i++){
if(i%j==0){
isPrime = false;
}
}
if(isPrime){
tally++;
}
}
return tally;
}
//重构
public long countPrimes(int upTo){
long tally = 0;
for (int i=0; i<upTo; i++){
boolean isPrime = true;
if(isPrime(i)){
tally++;
}
}
return tally;
}
private boolean isPrime(int number){
for (int j=2; j<number; i++){
if(i%j==0){
isPrime = false;
}
}
}
//集合流重构
public long countPrimes(int upTo){
return IntStream.range(1, upTo)
//.parallel() //并行
.filter(this::Prime)
.count();
}
public long isPrime(int number){
return IntStream.range(2, number)
.allMatch(x -> (number%x) != 0);
}
开闭原则
如不可变对象String,首次调用hashCode方法缓存了生成的哈希值
ThreadLocal<DateFormat> localFormatter = ThreadLocal.withInitial(()->new SimpleDateFormat());
DateFormat formatter = localFormatter.get();
AtomicInteger threadId = new AtomicInteger();
ThreadLocal<Integer> localId = ThreadLocal.withInitial(()->threadId.getAndIncrement());
int idForThisThread = localId.get();
依赖反转原则
细节反而以来抽象(如上层逻辑依赖底层抽象)
public List<String> findHeadings(Reader input){
try(BufferedReader reader = new BufferedReader(input)){
return reader.lines()
.filter(line -> line.endsWith(":"))
.map(line -> line.substring(0, line.length()-1))
.collect(toList());
}catch(IOException e){
throw new HadingLookuoException(e);
}
}
//重构
public List<String> findHeadings(Reader input){
return withLinesOf(reader.lines(), lines -> lines.filter(line -> line.endsWith(":"))
.map(line -> line.substring(0, line.length()-1))
.collect(toList(), HadingLookuoException::new));
}
public <T> T withLinesOf(Reader input, Function<Stream<String>> handler, Function<IoException, RuntimeException error){
try(BufferedReader reader = new BufferedReader(input)){
return handler.apply(reader.lines());
}catch(IOException e){
throw error.apply(e);
}
}
9.使用lambda表达式编写并发程序
使用Vert.x和RxJava框架,非阻塞式IO
回调
//Vert.x下聊天应用
//接受TCP连接
public class ChatVerticle extends Verticle{
public void start(){
vertx.createNetServer()
.connectHandler(socket -> { //回调
container.logger().info("socket connected");
socket.dataHandler(new User(socket, this));
}
)
.listen(10_000);
}
}
//处理用户连接
public class User implements Handler<Buffer>{
private static final Pattern newline = Pattern.compile("\\n");
private final NetSocket socket;
private final Set<String> names;
private final EventBus eventBus;
private Optional<String> name;
public User(NetSocket socket, Verticle verticle){
Vertx vertx = verticle.getVertx();
this.socket = socket;
names = vertx.sharedData().getSet("names");
eventBus = vertx.eventBus();
name = Optional.empty();
}
@Override
public void handler(Buffer buffer){
newline.splitAsStream(buffer.toString())
.forEach(line -> {
if(!name.isPresent())
setName(line);
else
handlerMessage(line);
});
....
}
//注册聊天消息
eventBus.registerHandler(name, (Message<String> msg) -> {
sendClient(msg.body);
} );
//发送聊天信息
eventBus.send(user, name.get()+">"+message);
//群发消息
private void broadcastMessage(String message){
String name = this.name.get();
eventBus.publish(name+".followers", name+">"+message);
}
//接受群发消息
private void followUser(String user){
eventBus.registerHandler(user+".followers", (Message<String> msg) -> {
sendClient(msg.body);
} );
}
消息传递架构
Vert.x通过复制发送的消息,避免共享状态。易于测试,隔离错误,不必重启JVM,重启本地Verticle对象即可。
@Test //聊天程序服务端
public void canMessageFriend(){
withModule(this::messageFriendWithModule);
}
private void messageFriendWithModule(){
withConection(richard -> {
checkBobReplies(richard);
richard.write("richard\n");
messageBob(richard);
});
}
private void checkBobReplies(NetSocket richard){
richard.handler(data -> {
assertEquals("bob>oh its you!", data.toString());
moduleTestComplete();
});
}
private void messageBob(NetSocket richard){
withConection(messageBobWithConnection(richard));
}
private Handler<NetSocket> messageBobWithConnection(NetSocket bob){
return bob -> {
checkRichardMessageYou(bob);
bob.write("bob\n");
vertx.setTimer(6, id -> richard.write("bob<hai"));
};
}
private void checkRichardMessageYou(NetSocket bob){
bob.handler(data -> {
assertEquals("richard>hai", data.toString());
bob.write("richard<oh its you!");
});
}
使用Future并行操作
public Album lookupByName(String albumName){
FUture<Credentials> trackLogin = loginTo("track");
FUture<Credentials> artistLogin = loginTo("artist");
try{//上两者阻塞
Future<List<Track>> tracks = lookupTracks(albumName, trackLogin.get());
Future<List<Artist>> aritists = lookupTracks(albumName, artistLogin.get());
return new Album(albunName, tracks.get(), artitsts.get());
}catch(InterruptedException | ExecutionException e){
throw new AlbumLookupException(e.getCause());
}
}
//使用CompletableFuture
public Album lookupByName(String albumName){
CompletaleFuture<List<Artist>> artistLookup = loginTo("artist")
.thenCompose(artistLogin -> lookupArtists(album, artistLogin));
return loginTo("track")
.thenCompose(trackLogin -> lookupTracks(album, trackLogin))
.thenCombine(artistLookup, (tracks, artists) -> new Album(albunName, tracks, artitsts) )
.join();
}
//创建CompleteableFuture
CompleteableFuture<Artist> createFuture(String id){
CompleteableFuture<Artist> future = new CompleteableFuture<>();
startJob(future);//在另外线程模型中后续赋值
return future;
}
//提供值
future.complete(artist);
//异步执行
CompleteableFuture<Track> lookupTrack(String id){
return CompleteableFuture.supplyAsync(()->{
...
return track;
}, service);
}
//出错时
future.completeExceptionally(AlbumLookupException("Unable to find "+name));
响应式编程
//RxJava 组合异步和基于事件的系统流程,处理数据流
public Observable<Artist> search(String searchedName, String searchedNationality, int maxResults){
return getSavedArtists()
.filter(name -> name.contains(searchedName))
.flatMap(this::lookupArtist)
.filter(artist -> artist.getNationality().contains(searchedNationality))
.tack(maxResults);
}
//传值
observer.onNext("a");
observer.onCompleted();
//Error
observer.onError(new Exception());