20145215《Java程序设计》第7周学习总结

20145215《Java程序设计》第七周学习总结

教材学习内容总结

Lambda

认识Lambda语法

  • Lambda语法概述:
  1. Arrays的sort()方法可以用来排序,在使用sort()时,需要操作java.util.Comparator来进行说明,这样一来,语法就显得有些冗长。在JDK8中,可以使用Lambda语法改变Arrays.sort()的可读性:
Comparator<String> byLength = 
      (String name1,String name2)->name1.length() - name2.length(); 

编译器可以从byLength变量的声明类型,推断name1与name2的类型,因此可以再简化为:

Comparator<String> byLength = (name1,name2)->name1.length() - name2.length();
  1. 方法参考的特性,在重用现有API上扮演了重要角色。重用现有方法实作,可避免到处写下Lambda运算式,方法参考不仅避免了重复撰写Lambda表达式,也可以让程序代码更清楚.
  • Lambda表达式与函数接口:
  1. 等号右边是Lambda表示式(Expression),等号左边是作为Lambda表示式的目标型态(Target type)
Comparator<String> byLength = 
      (String name1,String name2)->name1.length() - name2.length(); 
  1. 如果有目标型态的话,在编译器可推断出类型的情况下,就可以不写出Lambda表示式的参数型态,例如:
Comparator<String> byLength = (name1,name2)->name1.length() - name2.length();

Lambda表示式本身是中性的,不代表任何型态的实例,同样的Lambda表示式,可用来表示不同目标型态的对象操作,例如:

Fun<String,Integer> func = (name1,name2)->name1.length() - name2.length();
  1. JDK8的Lambda并没有导入新类型来作为Lambda表达式的类型,而是就现有的interface语法来定义函数接口,作为Lambda表达式的目标类型。Lambda表达式只关心方法签署上的参数与返回定义,但忽略方法名称,有个新标注@FunctionalInterface在JDK8中被引入,它可以这么使用:
@FunctionalInterface
public iterface Func<P,R>{
    R apply(P p);
}

如果接口使用了@FunctionalInterface来标注,而本身并非函数接口的话,就会引发编译错误。

  • Lambda遇上this与final:Lambda表示式并不是匿名类别的语法蜜糖,如果Lambda表示式中捕获的局部变量本身等效于final局部变量,可以不用在局部变量上加上final,JDK8特意禁止在Lambda中修改局部变量的值。
  • 方法与构造函数参考:方法参考(Method references)可以避免你到处写下Lambda表示式,尽量运用现有的API实作,也可以改善可读性。
  • 接口默认方法:在JDK8中,interface定义时可以加入默认操作,或者称为默认方法(Default methods),forEach()方法本身已有操作,所以不会破坏Iterable现有的其他操作。在默认方法中不能使用数据成员,因为接口本身不能定义数据成员,也就是默认方法中不能用直接变更状态的流程。

Functional与Stream API

  • 使用Optional取代null:null的最根本问题在于语义含糊不清,调用方法时如果返回类型是Optional,应该立即想到它可能包含也可能不包含值,在Optional没有包含值的情况下,get会抛出NoSuchElementException,Optional的ofNullable()来衔接程式库中会传回null的方法。
  • 标准API的函数接口:基本上可以分为Consumer、Function、Predicate与Supplier四个类型
  1. 如果需要的行为是接受一个自变量,然后处理后不传回值,就可以使用Consumer接口,接受了自变量但没有传回值,这行为就像纯綷消耗了自变量,就是命名为Consumer的原因。如果真的有结果产生,就是以副作用(Side effect)形式呈现,例如:
Arrays.asList("Justin","Monica","Irene").forEach(out::println);
  1. 接受一个自变量,然后以该自变量进行计算后传回结果,就可以使用Function接口,行为就像是数学函数y=f(x),给予x值计算出y值的概念,因此命名为Function。
  2. 接受一个自变量,然后只传回boolean值,也就是根据传入的自变量直接论断真假的行为,就可以使用Predicate函数接口。
  3. 需要的行为是不接受任何自变量,然后传回值,那可以使用Supplier函数接口。
  • 使用Stream进行管道操作:
  1. 绝大多数的Stream并不需要呼叫close()方法,JDK8中要close()的是Files.lines()、Files.list()与Files.walk()方法。
  2. JDK8引入了Stream API,也引入了管道操作风格,一个管道基本上包括了几个部分:来源、零或多个中间操作、一个最终操作。

时间与日期

认识时间与日期

  • 时间的度量:
  1. 就目前来说,即使标注为格林威治标准时间(GMT时间),实际上谈到时间指的是UTC时间。
  2. 秒的单位定义是基于TAI,也就是铯原子辐射振动次数。
  3. UTC考虑了地球自转越来越慢而有闰秒修正,确保UTC与UT相差不会超过0.9秒。
  4. Unix时间是1970年1月1日00:00:00 为起点而经过的秒数,不考虑闰秒。
  • 年历简介:
  1. 儒略历:修正了罗马历隔三年设置一闰年的错误,改采四年一闰。
  2. 格里高利历:改革了儒略历。
  3. ISO 8601标准:并非年历系统,而是时间日期表示方法的标准,用以统一时间日期的数据交换格式。
  • 认识时区:牵涉到地理、法律、经济、社会甚至政治等问题
  1. UTC偏移(offset)。
  2. 有些国家的领土横跨的经度很大,一个国家有多个时间反而造成困扰,因而不採取每15度偏移一小时的作法。
  3. 实施日光节约时间(Daylight saving time)。

认识Date与Calender

  • 时间轴上瞬间的Date:
  1. 如果想要取得系统时间,方法之一是使用System.currentTimeMillis()方法,返回的是long类型整数,代表1970年1月1日0时0分0秒0毫秒至今经过的毫秒数,有人会使用Date实例来取得系统时间描述,不过Date也是偏向机器的时间观点,例如:DateDemo.java

  2. Date有两个构造函数可以使用,一个可使用epoch毫秒数构建,另一个为无自变量构造函数,内部亦是使用System.currentTimeMillis()取得毫秒数,调用getTime()可取得内部保存的epoch毫秒数值。

  3. 不建议使用toString()来得知年月日等栏位资讯,有关于字串时间格式的处理,不再是Date的职责。

  • 格式化时间日期的DateFormat:
  1. 字串时间格式的处理,职责落到了java.text.DateFormat身上,其操作类别java.text.SimpleDateFormat,可以直接建构SimpleDateFormat实例,或使用DateFormat的getDateInstance()、getTimeInstance()、getDateTimeInstance()等静态方法。

  2. 直接构建SimpleDateFormat的好处是,可使用模式字符串自定义格式。

  • 处理时间日期的Calendar:
  1. 想要取得某个时间日期资讯,或者是对时间日期进行操作,可以使用Calendar实例,通过Calendar的getInstance()取得的Calendar实例,默认就是取得GregorianCalendar实例,例如:
Calendar calendar = Calendar.getInstance();
  1. 取得Calendar实例后,可以使用getTime()取得Date实例,如果想要取得年月日等日期时间字段,可以使用get()方法指定Calender上的字段枚举常数:
out.println(calendar.get(Calendar.YEAR));
out.println(calendar.get(Calendar.MOUNTH));
out.println(calendar.get(Calendar.DATE));
  1. 如果要设定时间日期等字段,不要对Date设定,应该使用Calendar,同样地,月份的部分使用枚举常数设定:
Calendar calendar = Calendar.getInstace();
calendar.set(2014,Calendar.MAY,26);    //2015/5/26
out.println(calendar.get(Calendar.YEAR));     //2014
out.println(calendar.get(Calendar.MOUNTH));   //Calendar.MAY的值4
out.println(calendar.get(Calendar.DATE));     //26
  1. 在取得一个Calendar的实例后,可以使用add()方法,来改变Calendar的时间:
calendar.add(Calendar.MONTH,1);  //Calendar时间加一个月
calendar.add(Calendar.HOUR,3);   //Calendar时间加三小时
calendar.add(Calendar.YEAR,-2);  //Calendar时间减两年
calendar.add(Calendar.DATE,3);   //Calendar时间加三天
  1. 如果打算只针对日期中某个字段加减,则可以使用roll()方法:
calendar.roll(Calendar.DATE,1);  //只对日字段加1
  • 设定TimeZone:
  1. 使用java.util.TimeZone的getDefault()来取得默认时区信息。
  2. 想要取得指定时区的TimeZone实例,可以使用ID字串,例如:
TimeZone taipeiTz = TimeZone.getTimeZone("Asia/Taipei");

JDK8新时间日期API

  • 机器时间观点的API:
  1. Date名称看来像是人类的时间概念,实际却是机器的时间概念,混淆机器与人类时间观点会引发的问题之一像是日光节约时间。
  2. 不该使用Date实例的toString()来得知人类观点的时间信息,Date实例应该只代表机器观点的时间资讯,真正可靠的资讯只有内含的epoch毫秒数。
  • 人类时间观点的API:
  1. 对于片段的日期时间,JDK8新时间与日期API有LocalDateTime()、LocalDate()、LocalTime()等类来定义,这些类基于ISO 8601年历系统,是不具时区的时间与日期定义。
  2. 在新的时间与日期API中,UTC偏移量与时区的概念是分开的,offsetDateTime单纯代表UTC偏移量,使用ISO 8601。如果只想表示2014年,可以使用Year,如果想表示2014/5,可以使用YearMonth,如果只想表示5月,可以使用Month,如果想表示5/4,可以使用MonthDay,其中Month是enum型,如果你想要取得代表月份的数字,不要使用oridinal()方法,因为oridinal()是enum在定义时的顺序,从0开始,想要取得代表月份的数要通过getValue()方法。
  • 对时间的运算:period与Duration乍看有些难区别,period是日期差,between()方法只接受LocalDate,不表示比“日”更小的单位。然而Duration是时间差,between()方法可以接受LocalDateTime()、LocalDate()、LocalTime(),不表示比“天”更大的单位。

教材学习中的问题和解决过程

  • LambdaDemo.java实现逆序输出,只需将sort()方法中的name1和name2互换一下即可

  • 课本上DateFormatDemo.java运行结果有些问题,正确的结果short应该是如图所示:

  • 关于Calendar的补充:java.util.Calendar是个抽象类,是系统时间的抽象表示,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。与其他语言环境敏感类一样,Calendar 提供了一个类方法 getInstance,以获得此类型的一个通用的对象。Calendar 的 getInstance 方法返回一个 Calendar 对象,其日历字段已由当前日期和时间初始化。

  • 在使用Calendar中有些陷阱,很容易掉下去:

  1. Calendar的星期是从周日开始的,常量值为0。
  2. Calendar的月份是从一月开始的,常量值为0。
  3. Calendar的每个月的第一天值为1。

代码调试中的问题和解决过程

  • 我自己编写了一个测试Date的代码:
public class DateTest {
    public static void main(String args[]) {
        DateTest nowDate = new DateTest();
        nowDate.getSystemCurrentTime();
        nowDate.getCurrentDate();
    }

    public void getSystemCurrentTime() {
        System.out.println("----获取系统当前时间----");
        System.out.println("系统当前时间 = " + System.currentTimeMillis());
    }

    public void getCurrentDate() {
        System.out.println("----获取系统当前日期----");
        //创建并初始化一个日期(初始值为当前日期)
        Date date = new Date();
        System.out.println("现在的日期是 = " + date.toString());
        System.out.println("自1970年1月1日0时0分0秒开始至今所经历的毫秒数 = " + date.getTime());
    }
}

虽然代码成功运行,但发现现在已经不建议使用toString()的方法,因为这已经不再是Date的职责,后来修改了代码,用了DateFormat类,代码依然运行成功。

心得体会

不知不觉,厚厚的Java学习笔记已经学完一半了,看似学了很多,但我感觉自己还是有很多地方学的不是很明白。很多时候看书是看懂了,但当自己真正要去编写程序的时候却无法很好的利用曾经学过的知识,归根结底还是实践的太少,无法把这些学过的知识真正的运用起来。我曾经看过一篇关于Java学习的帖子,那里面有人这样说道,多做项目,多写代码,只有在项目开发当中遇到问题再去透彻的学习才能提升,没有目标的学习只会浪费时间和透支精力,熟能生巧,代码写的多了,技术自然就提升了。从他的话中,我又有了自己的一些感悟,学习Java最笨的方法是多读优秀的编程书籍;稍好的办法是实践出真知,多实践,实践的过程中遇到问题了再回来看书;最好的办法是在你实践的过程中,把你所做的东西表达出来,你要让别人看懂,你就得能够理清你所学东西。我也希望自己以后的代码可读性更强,能够让更多人看懂,到那时我的水平可能就又提升了一个层次!

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时 编写了Hello Java代码
第一周 100/100 2/2 12/12 编写了Hello Java代码
第二周 200/300 2/4 15/27 理解了printf和println的区别
第三周 450/750 1/5 22/49 对对象有了更深层次的理解
第四周 869/1619 1/6 28/77 对对象的三大特征有了更全面的认识
第五周 1123/2742 1/7 25/102 学会了异常处理
第六周 863/3605 2/9 30/132 理解了线程
第七周 505/4110 2/11 28/160 掌握了日期和时间的运用

【附1】本周学习的代码已经成功托管,截图如下:

【附2】利用wc统计代码行数,截图如下:

参考资料

posted @ 2016-04-17 18:15  L.X.M  阅读(376)  评论(10编辑  收藏  举报