自主学习报告第一周F_part

自主学习报告第一周

Java8新特性总览
  • 接口的默认方法
  • Lambda表达式
  • 函数式接口
  • 方法引用
  • 变量作用域
  • 访问局部变量
  • 访问对象字段与静态变量
  • 访问接口默认方法
  • Date API
  • Annotation注解
接口的默认方法:从Java8开始,支持在抽象方法中定义非抽象方法,只需用default关键字将该方法声明为默认方法。
Lambda表达式: Lambda表达式是一个传递操作的表达式,实际上就是一个匿名函数,通过Lambda表达式可以省去很多多余的步骤,简化代码,同时使代码更易于阅读。

假如我要创建一个线程,该线程用于打印”Hello,world”操作,传统方法是写一个类继承Runable类重写run方法,创建该类的对象,用这个对象创建一个线程,启动线程打印“Hello,world”,示例代码如下:

Class DoWork implement Runnable{

    public void run(){
        Systen.out.println("Hello,world");
    }
}

启动线程

new Thread(new DoWork()).start();

引入Lambda表达式之后我们有更简单的方式实现它

new Thread(() -> System.out.println("Hello,world")).start;

通过这种方式我们可以很直观地将原本涉及多个类的函数写进一个方法里面。这里包含了我们下一节要讲的内容:函数式接口。

函数式接口:只包含一个抽象方法的接口我们都称为函数式接口。上一节讲的Runnable接口就是一个函数式接口,它包含一个run抽象方法。

predicate:接收一个参数,返回一个布尔值。

supplier:不接受任何参数,返回一个结果。

function:接收一个参数,返回一个结果。

comparator:接收两个参数,返回一个布尔值。

comsumer:接收一个参数,不返回任何值,但可以对该参数对象的内部值进行修改。

下面对这些接口进行简单的应用,模拟的场景是对工资的扣除个人所得税操作、显示信息操作以及工资排序操作,由于本人经验严重不足,对场景的模拟和想象可能不能更加淋漓尽致地发挥函数式接口的作用,请多包涵。

/**
 * 
 */
package system;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import entity.Personal;


/*
 * 
 * 工资  如果工资>=3500 扣除个税
 *
 * predicate & function | predicate &function
 *
 * 显示个人信息
 * 
 * consumer
 *
 * 通过工资高低进行排序
 *
 * Comparator
 * 
 */
public class BankSystem {

    public static void main(String[] args){
        Personal pers = new Personal("Amy", 4000.0);

        //通过function可以接受一个参数,返回一个结果,通过这种方法可以将对象读入,改变对象的某个值再输出
        pers = doWorkAsFunction(pers, n -> n.getFirstSalary() >= 3500, n -> {n.setLastSalary(n.getFirstSalary()*0.8);return n;});

        //通过consumer可以读入一个参数,直接改变该参数的内部值。相对而言function更通用,它可以接收任意值返回任意值,而consumer主要用于改变对象的内部值
//      doWorkAsConsumer(pers, n -> n.getFirstSalary() >= 3500, n -> n.setLastSalary(n.getFirstSalary()*0.9));

        //展示个人信息
        show(pers, n -> 
        {
            System.out.println("name:" + n.getName());
            System.out.println("firstSalary:" + n.getFirstSalary());
            System.out.println("lastSalary:" + n.getLastSalary());

        });

        //按照工资高低进行排序
//      Stream<Personal> persStream = Stream.of(new Personal("Amy", 1000), new Personal("Aya", 5000), new Personal("Boder", 2000), new Personal("BiBy" , 4000))
//              .peek(m -> doWorkAsConsumer(m, n -> n.getFirstSalary() >= 3500, n -> n.setLastSalary(n.getFirstSalary()*0.9)));

        Stream<Personal> persStream = Stream.of(new Personal("Amy", 1000), new Personal("Aya", 5000), new Personal("Boder", 2000), new Personal("BiBy" , 4000))
                .map(m -> doWorkAsConsumer(m, n -> n.getFirstSalary() >= 3500, n -> n.setLastSalary(n.getFirstSalary()*0.9)));

        persStream.sorted((p1, p2) -> Double.compare(p1.getLastSalary(), p2.getLastSalary())).forEach(n -> System.out.println(n.getName() + n.getLastSalary()));
    }

    public static Personal doWorkAsFunction(Personal pers, Predicate<Personal> perd, Function<Personal, Personal> func){

        pers.setLastSalary(pers.getFirstSalary());

        if(perd.test(pers)){

            pers =  func.apply(pers);
        }

        return pers;
    }

    public static Personal doWorkAsConsumer(Personal pers, Predicate<Personal> perd, Consumer<Personal> cons){

        pers.setLastSalary(pers.getFirstSalary());

        if(perd.test(pers)){

            cons.accept(pers);
        }

        return pers;

    }

    public static void show(Personal pers, Consumer<Personal> coms){

        coms.accept(pers);

        System.out.println("以上为您的个人信息");
    }
}

从代码上就可以看出来,Function接口和comsumer接口都可以对对象进行操作,Function接口主要是接受一个参数,返回一个结果,该结果不一定是对象本身,也可以是其他任意的泛型,因此相对于comsumer而言更加通用,而comsumer是针对于改变对象内部的值,因此在后面项目中,假如仅仅是需要改变对象的状态就使用consumer吧。

展示信息部分明显是错误使用的例子,通过这种方式只是想说明comsumer接受一个参数并不一定要改变该对象的状态,也可以不对对象进行任何操作,不过这样做的话comsumer就含无意义,只是增加冗余代码而已。

对工资进行排序操作部分的代码涉及到后面要讲的知识点,peek和map,这两个都可以帮助改变流元素,他们的关系和comsumer和function的关系类似,peek不需要返回值,仅仅将函数应用于每个元素,而map需要返回一个结果,因此在使用map的时候假如删去doWorkAsXxxx里的return语句,编译器会告诉你,不能返回一个void result。

此外包括原有接口,和为Java8新增的接口,函数式接口还有很多,这里只是举例取出几个比较常用的接口进行说明,其他接口只要了解接口用途,熟悉lambda表达式基本用起来都没有任何问题。

方法引用:方法引用是通过::引用方法或者构造器。每一个方法引用都是对Lambda表达式的一个简化。例如String::CompareToIgnoreCase就相当于(x, y) -> x.String.CompareToIgnoreCase(y)

方法引用有以下四种情况:

1.对象::实例方法
2. 类::静态方法
3. 类::实例方法
4. 类::new

方法引用如何传参?

这个问题困惑了我很久,看了很多博客,似乎都没说到点上(或者是隐性说到了,愚钝的我没有察觉)。之后看到了某篇学习笔记突然恍然大悟。在纠结这个问题的时候,我们先要明确方法引用是什么,方法引用不是调用一个方法,而是对Lambda表达式的一个简化写法。比如一个Lambda表达式x -> System.out.println(x),通过方法引用可以表达为System.out::println,这样一个表达式单独存在是没有任何意义的,我们知道目前Java8的Lambda表达式唯一的作用体现在函数式接口,所以任何一个Lambda表达式都必须显性或者隐性地赋值给一个函数式接口,像上述的表达式,我们可以赋值给一个接收一个参数,不返回任何结果的函数式接口,consumer正好符合我们的要求,则Consumer cons = System.out::println,获得这样一个接口之后要调用它的accept方法才能执行Lambda表达式里的语句,调用方法cons.accept(参数),通过这一步,问题就迎刃而解了,参数是在最后调用接口方法的时候才传进去的。跳出惯性思维来看问题就十分简单,我们写的Lambda表达式其实就是接口抽象方法的实现,因此参数的传入就理应由接口抽象方法接收。所以当我们企图通过::引用方法的时候,就不得不找到合适的函数式接口。假如开发人员并没有帮我们设计合适的函数式接口,我们也可以自己写符合我们自己需求的函数式接口,不过我觉得这样做并不能简化开发,除非设计Java的大牛们已经写好了满足我们需求的所有函数式接口,否则我觉得这个设计并不能让代码看起来更简洁,或者开发起来更高效,以上也是本人的一点拙见,若有愚钝之处,请轻喷。此外,假如调用者本身能够成为参数,jvm也会帮我们把它作为参数,这一点很难给出例子,只能在使用的时候体会出来。

下面是对方法引用的几个小例子

//对象::实例方法
Consumer<String> cons = System.out::println;

cons.accept("Hello,world");

//类::静态方法
Function<Integer, String> func = String::valueOf;

System.out.println(func.apply(100));

//类::实例方法
ICompare<String> Icom = String::compareToIgnoreCase;

System.out.println(Icom.comparing("aa", "abc"));

其中ICompare是自定义的函数式接口,其语法如下

public interface ICompare <T>{

    public int comparing(T a, T b);
}

这些方法引用的方法主体被赋值给一个函数式接口,通过函数式接口可以被随处调用。

下面是对构造器引用的一个例子

//构造器应用的一个用法
List<String> list = Arrays.asList("Aya", "Amy", "Ada", "Bob");  

Object[] persList = list.stream().map(Personal::new).toArray();

for(Object p : persList){
    System.out.println(((Personal)p).getName());
}

这里用的还是上面工资的例子。上面我们在创建多个对象的时候是一个一个手动new出来的,在这里我们可以直接实例化一个包含我们设定的人物的名字的字符串链表,通过map对每一个字符串进行实例化,在这里即使Personal中有多个构造器,编译器会帮我们选择合适的构造器。

访问局部变量
public static void doWork(String str, int count){

    Runnable r = () -> {

        for(int i=0; i<count; i++) System.out.println(str);
    };

    (new Thread(r)).start();
}

通过这种方式,我们访问了原本并不定义在Lambda表达式内的变量,这样访问的约束跟匿名函数访问的约束一样,即访问的变量必须是final变量,在Java8中对匿名函数访问的变量的约束相对放宽,访问变量可以不用final声明,不过即使没有final声明,只要在匿名函数或者Lambda表达式中访问到,该变量就被隐性地加上final声明,成为既成事实上的final变量。

像上面这个方法,当str和count被Lambda表达式捕获,这两个变量在Lambda表达式的作用域内(doWork方法内)就成为了既成事实的final变量。在这个区域内这两个变量一旦改变编译器都会报出错误。有一个很有趣的细节,当我们在Lambda作用域内对str进行这样的赋值str = str编译器也会报错。说明底层对final变量的检查是通过是否二次赋值来判断的。所以即使只要在Lambda作用域内,哪怕是Lambda表达式之后,都会被判断为非不可变值变量。

当然,并不是毫无办法改变final变量的值。上面我们说了,对final变量的检查是通过检查是否二次赋值来进行判断的,那想要改变final变量的值,我们可以不改变变量的引用,而改变变量的状态。假如我们要改变一个数值型的final变量,我们可以通过把变量放进一个长度为1的数组,之后再Lambda表达式内不改变变量的引用,而改变数组内第一个索引的值。

public static void doWork(String str, int[] count){

    count[0] = 3;

    Runnable r = () -> {

        for(int i=0; i<count[0]; i++) System.out.println(str);
    };

    (new Thread(r)).start();
}

假如你要改变字符串你可以将字符串封装进对象,在Lambda作用域内不改变变量的引用,而改变对象的状态。


public static void doWork(MyString str, int[] count){

    count[0] = 3;

    str.setString("Hahaha");

    Runnable r = () -> {

        for(int i=0; i<count[0]; i++){
            System.out.println(str.getString());
        }
    };
    (new Thread(r)).start();

}

通过这些取巧的方式,可以改变final变量的值,但这并不是线程安全的,因此在涉及线程的问题时,请三思而后行。

接口实现和父类继承的冲突解决

在Java8中,允许在接口中定义默认方法或者静态方法,就会导致一个问题的重新定义,就是“同名同参函数,谁优先”。

  1. 当某个类继承了父类,同时实现了一个接口,父类与该接口存在同名同参函数,此时遵循“类优先”原则,即不论接口中的方法是否有实现,一律以父类方法为继承的方法。
  2. 当某个类同时实现两个接口,这两个接口存在同名同参函数,不论如何是否实现,都需要开发人员手动解决这种冲突,即重写该方法,可以在重写方法内指明实现那个方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值