42

Lambdas and Streams in Java SE 8: Making Bulk Operations simple - Simon Ritter

Embed Size (px)

Citation preview

Lambdas  &  Streams  In  JDK8  Making  Bulk  Opera/ons  Simple  

Simon  Ri6er  Head  of  Java  Technology  Evangelism  Oracle  Corp.    Twi6er:  @speakjava  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Safe  Harbor  Statement  The  following  is  intended  to  outline  our  general  product  direcTon.  It  is  intended  for  informaTon  purposes  only,  and  may  not  be  incorporated  into  any  contract.  It  is  not  a  commitment  to  deliver  any  material,  code,  or  funcTonality,  and  should  not  be  relied  upon  in  making  purchasing  decisions.  The  development,  release,  and  Tming  of  any  features  or  funcTonality  described  for  Oracle’s  products  remains  at  the  sole  discreTon  of  Oracle.  

3  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

1996 … 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 1.0 5.0 6 7 8 java.lang.Thread  

java.util.concurrent  (jsr166)  

Fork/Join  Framework  (jsr166y)  

Project  Lambda  Concurrency  in  Java  

Phasers,  etc  (jsr166)  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Lambdas  In  Java    

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

The  Problem:  External  IteraTon  

List<Student>  students  =  ...  double  highestScore  =  0.0;  for  (Student  s  :  students)  {      if  (s.getGradYear()  ==  2011)  {          if  (s.getScore()  >  highestScore)              highestScore  =  s.score;      }  }  

•  Our  code  controls  iteraTon  •  Inherently  serial:  iterate  from  

beginning  to  end  •  Not  thread-­‐safe      

•  Business  logic  is  stateful  •  Mutable  accumulator  variable  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Internal  IteraTon  With  Inner  Classes  

•  IteraTon  handled  by  the  library  •  Not  inherently  serial  –  traversal  may  be  done  in  parallel  

•  Traversal  may  be  done  lazily  –  so  one  pass,  rather  than  three  

•  Thread  safe  –  client  logic  is  stateless  •  High  barrier  to  use  

– SyntacTcally  ugly  

More  FuncTonal  double  highestScore  =  students      .filter(new  Predicate<Student>()  {          public  boolean  op(Student  s)  {              return  s.getGradYear()  ==  2011;          }      })      .map(new  Mapper<Student,Double>()  {          public  Double  extract(Student  s)  {              return  s.getScore();          }      })      .max();  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Internal  IteraTon  With  Lambdas  List<Student>  students  =  ...  double  highestScore  =  students                        .filter(Student  s  -­‐>  s.getGradYear()  ==  2011)                        .map(Student  s  -­‐>  s.getScore())                        .max();  

•  More  readable  •  More  abstract  

•  Less  error-­‐prone  

NOTE:  This  is  not  JDK8  code  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Lambda  Expressions  

•  Lambda  expressions  represent  anonymous  funcTons  – Same  structure  as  a  method  

•  typed  argument  list,  return  type,  set  of  thrown  excepTons,  and  a  body  

– Not  associated  with  a  class  • We  now  have  parameterised  behaviour,  not  just  values  

Some  Details  

double  highestScore  =  students.                      filter(Student  s  -­‐>  s.getGradYear()  ==  2011).                      map(Student  s  -­‐>  s.getScore())                      max();  

What  

How  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Lambda  Expression  Types  

•  Single-­‐method  interfaces  are  used  extensively  in  Java  – DefiniTon:  a  func2onal  interface  is  an  interface  with  one  abstract  method  – Func2onal  interfaces  are  idenTfied  structurally  – The  type  of  a  lambda  expression  will  be  a  func2onal  interface  

•  Lambda  expressions  provide  implementaTons  of  the  abstract  method  

interface  Comparator<T>    {  boolean  compare(T  x,  T  y);  }    interface  FileFilter          {  boolean  accept(File  x);  }    interface  Runnable              {  void  run();  }    interface  ActionListener  {  void  actionPerformed(…);  }    interface  Callable<T>        {  T  call();  }  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Local  Variable  Capture  

•  Lambda  expressions  can  refer  to  effec2vely  final  local  variables  from  the  enclosing  scope  

•  EffecTvely  final:  A  variable  that  meets  the  requirements  for  final  variables  (i.e.,  assigned  once),  even  if  not  explicitly  declared  final  

•  Closures  on  values,  not  variables  

void  expire(File  root,  long  before)  {        root.listFiles(File  p  -­‐>  p.lastModified()  <=  before);  

}  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

What  Does  ‘this’  Mean  For  Lambdas?  

•  ‘this’  refers  to  the  enclosing  object,  not  the  lambda  itself  •  Think  of  ‘this’  as  a  final  predefined  local  •  Remember  the  Lambda  is  an  anonymous  func2on  

–  It  is  not  associated  with  a  class  –  Therefore  there  can  be  no  ‘this’  for  the  Lambda  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Referencing  Instance  Variables  Which  are  not  final,  or  effecTvely  final    class  DataProcessor  {      private  int  currentValue;        public  void  process()  {          DataSet  myData  =  myFactory.getDataSet();          dataSet.forEach(d  -­‐>  d.use(currentValue++));      }  }  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Referencing  Instance  Variables  The  compiler  helps  us  out    class  DataProcessor  {      private  int  currentValue;        public  void  process()  {          DataSet  myData  =  myFactory.getDataSet();          dataSet.forEach(d  -­‐>  d.use(this.currentValue++);      }  }  

‘this’  (which  is  effecTvely  final)  inserted  by  the  compiler  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Type  Inference  

•  The  compiler  can  oien  infer  parameter  types  in  a  lambda  expression  § Inferrence  based  on  the  target  funcTonal  interface’s  method  signature  

•  Fully  staTcally  typed  (no  dynamic  typing  sneaking  in)  – More  typing  with  less  typing  

List<String>  list  =  getList();  Collections.sort(list,  (String  x,  String  y)  -­‐>  x.length()  -­‐  y.length());  

Collections.sort(list,  (x,  y)  -­‐>  x.length()  -­‐  y.length());  

static  T  void  sort(List<T>  l,  Comparator<?  super  T>  c);  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Method  References  

•  Method  references  let  us  reuse  a  method  as  a  lambda  expression  

FileFilter  x  =  File  f  -­‐>  f.canRead();  

FileFilter  x  =  File::canRead;  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Constructor  References  

•  Same  concept  as  a  method  reference  – For  the  constructor  

Factory<List<String>>  f  =  ArrayList<String>::new;  

Factory<List<String>>  f  =  ()  -­‐>  return  new  ArrayList<String>();  

Replace  with  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Library  EvoluTon    

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Library  EvoluTon  Goal  

• Requirement:  aggregate  operaTons  on  collecTons  – New  methods  required  on  CollecTons  to  facilitate  this  

         

• This  is  problemaTc  – Can’t  add  new  methods  to  interfaces  without  modifying  all  implementaTons  – Can’t  necessarily  find  or  control  all  implementaTons  

int  heaviestBlueBlock  =  blocks                        .filter(b  -­‐>  b.getColor()  ==  BLUE)                        .map(Block::getWeight)                        .reduce(0,  Integer::max);  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

SoluTon:  Default  Methods  

• Specified  in  the  interface  • From  the  caller’s  perspecTve,  just  an  ordinary  interface  method  • Provides  a  default  implementaTon  

•  Default  only  used  when  implementaTon  classes  do  not  provide  a  body  for  the  extension  method  

•  ImplementaTon  classes  can  provide  a  be6er  version,  or  not  

interface  Collection<E>  {        default  Stream<E>  stream()  {            return  StreamSupport.stream(spliterator());      }    }  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Virtual  Extension  Methods  

•  Err,  isn’t  this  implemenTng  mulTple  inheritance  for  Java?      •  Yes,  but  Java  already  has  mulTple  inheritance  of  types  •  This  adds  mulTple  inheritance  of  behavior  too  •  But  not  state,  which  is  where  most  of  the  trouble  is  •  Can  sTll  be  a  source  of  complexity    

•  Class  implements  two  interfaces,  both  of  which  have  default  methods  •  Same  signature  •  How  does  the  compiler  differenTate?  

•  StaTc  methods  also  allowed  in  interfaces  in  Java  SE  8  

Stop  right  there!  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

FuncTonal  Interface  DefiniTon  

•  Single  Abstract  Method  (SAM)  type  • A  funcTonal  interface  is  an  interface  that  has  one  abstract  method  

– Represents  a  single  funcTon  contract  – Doesn’t  mean  it  only  has  one  method  

• @FunctionalInterface  annotaTon  – Helps  ensure  the  funcTonal  interface  contract  is  honoured  – Compiler  error  if  not  a  SAM  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Lambdas  In  Full  Flow:  Streams  

 

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Aggregate  OperaTons  

• Most  business  logic  is  about  aggregate  operaTons  – “Most  profitable  product  by  region”  – “Group  transacTons  by  currency”  

• As  we  have  seen,  up  to  now,  Java  uses  external  iteraTon  – Inherently  serial  – FrustraTngly  imperaTve  

•  Java  SE  8’s  answer:  The  Stream  API  – With  help  from  Lambdas  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Stream  Overview  

• AbstracTon  for  specifying  aggregate  computaTons  – Not  a  data  structure  – Can  be  infinite  

•  Simplifies  the  descripTon  of  aggregate  computaTons  – Exposes  opportuniTes  for  opTmisaTon  – Fusing,  laziness  and  parallelism  

At  The  High  Level  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Stream  Overview  

• A  stream  pipeline  consists  of  three  types  of  things  – A  source  – Zero  or  more  intermediate  operaTons  – A  terminal  operaTon  

•  Producing  a  result  or  a  side-­‐effect  

Pipeline  

int  total  =  transactions.stream()      .filter(t  -­‐>  t.getBuyer().getCity().equals(“London”))      .mapToInt(Transaction::getPrice)      .sum();  

Source  

Intermediate  operaTon  Terminal  operaTon  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Stream  Sources  

•  From  collecTons  and  arrays  – Collection.stream()  – Collection.parallelStream()  – Arrays.stream(T  array)  or  Stream.of()  

•  StaTc  factories  – IntStream.range()  – Files.walk()  

• Roll  your  own  – java.util.Spliterator  

Many  Ways  To  Create  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Stream  Sources  Provide  

• Access  to  stream  elements  • DecomposiTon  (for  parallel  operaTons)  

– Fork-­‐join  framework  

•  Stream  characterisTcs  – ORDERED  – SORTED  – DISTINCT  – SIZED  – NONNULL  – IMMUTABLE  – CONCURRENT  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Stream  Terminal  OperaTons  

•  The  pipeline  is  only  evaluated  when  the  terminal  operaTon  is  called  – All  operaTons  can  execute  sequenTally  or  in  parallel  – Intermediate  operaTons  can  be  merged  

•  Avoiding  mulTple  redundant  passes  on  data  •  Short-­‐circuit  operaTons  (e.g.  findFirst)  •  Lazy  evaluaTon  

– Stream  characterisTcs  help  idenTfy  opTmisaTons  •  DISTINT  stream  passed  to  distinct()  is  a  no-­‐op  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Maps  and  FlatMaps  Map  Values  in  a  Stream  

Map  

FlatMap  

Input  Stream  

Input  Stream  

1-­‐to-­‐1  mapping  

1-­‐to-­‐many  mapping  

Output  Stream  

Output  Stream  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Optional<T>  Reducing  NullPointerException  Occurrences   String  direction  =  gpsData.getPosition().getLatitude().getDirection();    String  direction  =  “UNKNOWN”;    if  (gpsData  !=  null)  {      Position  p  =  gpsData.getPosition();        if  (p  !=  null)  {          Latitude  latitude  =  p.getLatitude();            if  (latitude  !=  null)              direction  =  latitude.getDirection();      }  }  

String  direction  =  gpsData.getPosition().getLatitude().getDirection();  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Optional<T>  

•  Indicates  that  reference  may,  or  may  not  have  a  value  – Makes  developer  responsible  for  checking  – A  bit  like  a  stream  that  can  only  have  zero  or  one  elements  

Reducing  NullPointerException  Occurrences  

Optional<GPSData>  maybeGPS  =  Optional.ofNullable(gpsData);    maybeGPS.ifPresent(GPSData::printPosition);    GPSData  gps  =  maybeGPS.orElse(new  GPSData());    maybeGPS.filter(g  -­‐>  g.lastRead()  <  2).ifPresent(GPSData.display());  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  1  Convert  words  in  list  to  upper  case  

List<String>  output  =  wordList      .stream()      .map(String::toUpperCase)      .collect(Collectors.toList());  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  1  Convert  words  in  list  to  upper  case  (in  parallel)  

List<String>  output  =  wordList      .parallelStream()      .map(String::toUpperCase)      .collect(Collectors.toList());  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  2  

• BufferedReader  has  new  method  – Stream<String>  lines()  

 

Count  lines  in  a  file  

long  count  =  bufferedReader      .lines()      .count();  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  3  Join  lines  3-­‐4  into  a  single  string  

String  output  =  bufferedReader      .lines()      .skip(2)      .limit(2)      .collect(Collectors.joining());  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  4  Collect  all  words  in  a  file  into  a  list  

List<String>  output  =  reader      .lines()      .flatMap(line  -­‐>  Stream.of(line.split(REGEXP)))      .filter(word  -­‐>  word.length()  >  0)      .collect(Collectors.toList());  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  5  List  of  unique  words  in  lowercase,  sorted  by  length  

List<String>  output  =  reader      .lines()      .flatMap(line  -­‐>  Stream.of(line.split(REGEXP)))      .filter(word  -­‐>  word.length()  >  0)      .map(String::toLowerCase)      .distinct()      .sorted((x,  y)  -­‐>  x.length()  -­‐  y.length())      .collect(Collectors.toList());  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  6:  Real  World  Infinite  stream  from  thermal  sensor  

private  int  double  currentTemperature;  ...    thermalReader      .lines()      .mapToDouble(s  -­‐>                  Double.parseDouble(s.substring(0,  s.length()  -­‐  1)))      .map(t  -­‐>  ((t  –  32)  *  5  /  9)      .filter(t  -­‐>  t  !=  currentTemperature)      .peek(t  -­‐>  listener.ifPresent(l  -­‐>  l.temperatureChanged(t)))      .forEach(t  -­‐>  currentTemperature  =  t);  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Example  6:  Real  World  Infinite  stream  from  thermal  sensor  

private  int  double  currentTemperature;  ...    thermalReader      .lines()      .mapToDouble(s  -­‐>                  Double.parseDouble(s.substring(0,  s.length()  -­‐  )))      .map(t  -­‐>  ((t  –  32)  *  5  /  9)      .filter(t  -­‐>  t  !=  this.currentTemperature)      .peek(t  -­‐>  listener.ifPresent(l  -­‐>  l.temperatureChanged(t)))      .forEach(t  -­‐>  this.currentTemperature  =  t);  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.      

Conclusions  

•  Java  needs  lambda  statements    – Significant  improvements  in  exisTng  libraries  are  required  

• Require  a  mechanism  for  interface  evoluTon  – SoluTon:  virtual  extension  methods  

• Bulk  operaTons  on  CollecTons  – Much  simpler  with  Lambdas  

•  Java  SE  8  evolves  the  language,  libraries,  and  VM  together  

Simon  Ri6er  Oracle  CorporarTon    Twi6er:  @speakjava  

Copyright  ©  2014,  Oracle  and/or  its  affiliates.  All  rights  reserved.