Java 8 函数式编程
来源:互联网 发布:淘宝客微博推广教程 编辑:程序博客网 时间:2024/05/16 04:14
《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();
常用的操作
//collectList<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.minArtist 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();//StreamString 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;}//Streampublic 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());}//StreamMap<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 Twicepublic 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);}@Testpublic void twoLetterStringConvertedToUpperCase(){ String input = "ab"; String result = Testing.firstToUpperCase(input); assertEquals("Ab", result);}@Testpublic 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();//lambdaMacro 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);//lambdaCompress 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");//lambdaMoon 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(){ ... }}//lambapublic 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()); }}//使用CompletableFuturepublic 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();}//创建CompleteableFutureCompleteableFuture<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();//Errorobserver.onError(new Exception());
- Java 8 函数式编程
- java 8 函数式编程
- Java函数式编程
- Java函数式编程
- java函数式编程
- [Java 8] (1) 函数式编程简介
- Java 8函数式编程学习笔记
- 《Java 8函数式编程》 读书记录
- [Java 8] (1) 函数式编程简介
- [Java 8] (1) 函数式编程简介
- 跟上 Java 8 : 函数式编程
- 读《Java 8 函数式编程》
- Java 8 函数式编程学习笔记
- JAVA 8函数式编程(一):高阶函数
- Java函数式编程(一)
- Java函数式编程(二)
- Java函数式编程(三)
- 函数式编程在Java
- JSON basics
- Git使用:代码提交、同步,创建分支等
- POJ 2033 Alphacode 笔记
- Git之工作区和暂存区
- [LeetCode] Repeated DNA Sequences
- Java 8 函数式编程
- java的跨平台原理
- Compare Version Numbers字符串的应用+小技巧
- 计算日期到天数转换
- 栈的构建
- 剑指Offer第36题—Java版
- java.util.HashMap源码解析
- C#——面向对象——重载操作符——自定义转换
- 新一代学生成绩管理系统(C语言版)