写给Android开发者的Java 8简单入门教程

栏目: 编程语言 · Java · 发布时间: 6年前

内容简介:写给Android开发者的Java 8简单入门教程

这里首先需要说明下在Android Studio(下文中使用AS代指)上使用 Java 8会遇到的坑和问题。

一般我们在AS上应用Java 8的方式是通过使用Jack来进行编译,使用方法如下:

android {  
  ...  
  defaultConfig {  
	...  
	jackOptions {  
	  enabled true  
	}  
  }  
  compileOptions {  
	sourceCompatibility JavaVersion.VERSION_1_8  
	targetCompatibility JavaVersion.VERSION_1_8  
  }  
}

当我们使用Jack的时候会导致我们无法使用AS的Instant Run和DataBinding功能,而且不支持接口的默认方法。这不能成为我们放弃使用Java 8的理由。当我们正准备愉快地使用Java 8时,Google突然在17年3月的某一天宣布放弃Jack,对的,我们的Google又弃坑了。

不过放心,Google在AS 2.4版本中提供了对Java 8的官方支持,在AS 2.4中使用Java 8不会产生任何负面的影响。AS 2.4提供了测试版供开发者使用,如果有兴趣的话可以提前体验AS 2.4的新功能,到目前为止笔者已经愉快地使用AS 2.4开发一周多了。AS 2.4的下载地址为: https://developer.android.com/studio/preview/index.html

在AS 2.4中使用Java 8的方法:

android {  
  ...  
  compileOptions {  
	sourceCompatibility JavaVersion.VERSION_1_8  
	targetCompatibility JavaVersion.VERSION_1_8  
  }  
}

我们为什么要使用Java 8?

在回答这个问题前,我们现在看一段简单的Java代码。代码的功能是把一堆苹果按照重量进行排序。

我们先来看看苹果类代码:

public class Apple {  
	private int weight;  
	private String color;  
}

对苹果按照重量进行排序:

Collections.sort(apples, new Comparator<Apple>() {  
	@Override  
	public int compare(Apple o1, Apple o2) {  
		return o1.getWeight().compareTo(o2.getWeight());  
	}  
});

在Java 8里,你可以编写更为简洁的代码,这些代码读起来更接近问题的描述:

apples.sort(Comparator.comparing(Apple::getWeight));

上面这段代码非常简单,它念起来就是“对苹果排序,排序的条件是苹果的重量”。如果你研究过Lambda表达式的话,上面这段代码你就能够快速读懂,如果暂时理解不了的话,也没关系,笔者会慢慢带你深入了解的。

Java 8对硬件的影响:平常我们用的CPU都是多核的,在知乎上我看到过一句话就是:大部分Android App都没有充分利用多核的性能,具体是哪个问题的哪个回答我忘了,这句话十分有道理。因为绝大多数现有Java 程序都只使用其中一个内核,其它的都闲着,或者一小部分的处理能力来运行操作系统和一些系统相关的Service。

在Java 8之前,我们必须使用多线程才能使用多个内核。问题是,线程用起来很难,也很容易出错,当线程竞争同一资源时,性能会大打折扣。Java 5添加了工业级的构建模块,如线程池和并发集合。Java 7添加了分支/合并集合框架,使得并行变得更加实用,但使用起来依然很难,并且很容易产生无法预期的错误。而Java 8对并行有了一个更简单的思路,不过在使用的使用仍然需要遵守一些规则,下面笔者会谈到。

Java 8的新特性

Java从函数式编程中引入的两个核心思想:将方法和Lambda作为一等值,以及在没有可变共享时,函数或方法可以有效、安全地并发执行。

下面我们来简单介绍下Java 8的一些重要概念:

  1. Stream API
  2. 行为参数化
  3. 接口中的默认方法

流(Stream):

Stream是Java 8提供的新API,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来看,你可以把把他们看成遍历数据集合的高级迭代器。此外Stream还可以透明地并行处理(parallelStream方法)。

Stream API和Java 现有的Collection(集合)API的行为差不多:它们都能够访问数据项目的序列。它们的区别是:Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的运算。这里的关键点在于,Stream允许并提倡并行处理一个Stream中的元素。 如果需要处理的数据量十分庞大的话,推荐使用并行流来进行处理。

流的简短定义是:从支持数据处理操作的源生成的元素序列 。

  • 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素。但流的目的在于表达计算,集合讲的是数据,流讲的是计算。
  • 源——流会使用一个提供数据的源,如集合、数组、输入流/输出资源。从有序集合生成流时会保留原有的顺序。
  • 数据处理操作——流的数据处理功能支持类似于数据库的操纵,以函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可以并行。
  • 流水线——很多流操作本身会返回一个流,许多个操作就可以链接起来,形成一个大的流水线。流水线可以看作对数据源进行数据库式查询。
  • 内部迭代——与使用显式迭代的集合不同,流的迭代操作是在背后进行的。流只能遍历一次,遍历完之后,这个流就已经被消费掉了。可以从数据源中重新获取一个流。

流操作:

  • 中间操作:可以连接起来的流操作(返回结果是流)。
  • 终端操作:关闭流的操作称为终端操作(其返回结果不是流)。

行为参数化:

行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力

行为参数化主要有以下这些特点:

  • 行为参数化可以让代码更好地适应不断变化的要求,减轻未来的工作量。
  • 传递代码,是将新的行为作为参数传递给方法。但在Java8之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成啰嗦的代码,在Java8之前可以用匿名类来减少。
  • Java API包含很多可以用不同行为参数化的方法,包括 排序 、线程和GUI处理等。

在Java 8中行为参数化主要是通过Lambda表达式和函数式接口来实现的。

Lambda概念

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有可以抛出异常的列表。

  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写的少而想得多。
  • 函数——Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  • 简洁——无需像匿名类那样写很多模版代码。

注: 函数式接口是指只带有一个抽象方法的接口(这里说成抽象方法主要是为了区分默认方法)。Java 8中提供了一系列通用的函数式接口供我们使用,通过使用这些系统提供的函数式接口可以避免重复定义类似接口。如:在Android中的OnClickListener接口可以使用Java 8中的Supplier\ 接口代替。

接口中的默认方法

在Java 8之前,接口中的方法是不能带有方法实现对的,这就意味着,一旦当我们的接口发布出去后,就不能轻易更改接口。因为更改接口会导致所有实现了该接口的类都需要更改,这会带来无法估量的问题。Java 8提供了默认方法这一特性,我们可以对方法添加默认的实现,这样一来,实现类就不必实现/覆盖这一方法。我们可以通过前面对苹果排序的代码来加深理解。

Collections.sort(apples, new Comparator<Apple>() {  
	@Override  
	public int compare(Apple o1, Apple o2) {  
		return o1.getWeight().compareTo(o2.getWeight());  
	}  
});

在Java 8里

apples.sort(Comparator.comparing(Apple::getWeight));

我们通过对比得知,在Java 8之前,我们需要通过Collections类sort方法来对列表进行排序。这不符合我们的理解,因为按照人类的理解,对列表进行排序的话,应该是属于列表的方法,而不是通过引入第二个类来实现。但是在Java 8之前,由于在接口中增加方法会导致所有的实现类都报错,所以为了保证兼容性,引入了Collections来实现对列表的操作。

而在Java 8中,List接口中提供了默认方法sort,这个方法的实际效果和Collections.sort是一样的。我们可以来看看List接口中的sort方法的具体实现:

default void sort(Comparator<? super E> c) {  
	Collections.sort(this, c);  
}

这个方法最终也是通过Collections.sort方法来实现排序的。通过这个方法,我们加深对默认方法的理解。

这里笔者要提醒大家的是:默认方法并不是我们的灵丹妙药,不能滥用默认方法。当我们发布一个公开的接口时,正确的做法是,做好充分的设计与验证才能进行发布。

Java 8的简单使用

我们通过一个简单的例子来介绍Java 8在Android上的应用。

定义一个菜肴列表,我们通过Stream对这个列表进行一些处理。

菜肴类:

public classDish{  
  
	public static final String MEAT = "MEAT";  
	public static final String OTHER = "OTHER";  
	public static final String FISH = "FISH";  
  
	@StringDef({MEAT,OTHER,FISH})  
	@Retention(RetentionPolicy.SOURCE)  
	public @interface Type{}  
  
	private final String name;  
	private final boolean vegetarian;  
	private final int calories;  
	private final @Type String type;  
  
	publicDish(String name,booleanvegetarian,intcalories,@Type String type){  
		this.name = name;  
		this.vegetarian = vegetarian;  
		this.calories = calories;  
		this.type = type;  
	}  
  
	publicStringgetName(){  
		return name;  
	}  
  
	publicbooleanisVegetarian(){  
		return vegetarian;  
	}  
  
	publicintgetCalories(){  
		return calories;  
	}  
  
	public @Type StringgetType(){  
		return type;  
	}  
  
	@Override  
	publicStringtoString(){  
		return name;  
	}  
}

菜肴菜单:

List<Dish> menu = Arrays.asList(  
		new Dish("pork",false,800,Dish.MEAT),  
		new Dish("beef",false,700,Dish.MEAT),  
		new Dish("chicken",false,400,Dish.MEAT),  
		new Dish("french fries",true,530,Dish.OTHER),  
		new Dish("rice",true,350,Dish.OTHER),  
		new Dish("season fruit",true,120,Dish.OTHER),  
		new Dish("pizza",true,550,Dish.OTHER),  
		new Dish("prawns",false,300,Dish.FISH),  
		new Dish("salmon",false,450,Dish.FISH));

现在我们需要实现一个功能:把卡路里大于300的菜肴筛选出来,并打印出菜肴名字。我们来看看普通实现和用Java 8实现的区别:

普通实现:

private void greaterThan300(){  
	for (Dish dish: menu){  
		if (dish.getCalories() > 300){  
			Log.d(TAG, "greaterThan300: " + dish.getName());  
		}  
	}  
}

Java 8实现:

private void greaterThan300Java8(){  
	menu.stream()  
			.filter(dish -> dish.getCalories() > 300)  
			.forEach(name ->Log.d(TAG, "greaterThan300: " + name));  
}

从上面的对比可以看到,使用Java 8编写的代码更直观,更符合逻辑思维,并且把循环和if去掉了。一旦习惯了Java 8的写法后,下面的代码我们一眼就能看出它的具体功能是什么,而上面的还需要花一点时间来理解。可能读者会觉得区别也不大,代码并没有少多少。那么我们再增加一个条件呢?例如把卡路里大于300的鱼类筛选出来,并打印出名字。

我们看看Java 8实现这个功能的代码

private void greaterThan300Java8(){  
	menu.stream()  
			.filter(dish -> dish.getCalories() > 300)  
			.filter(dish -> dish.getType() == Dish.FISH)  
			.forEach(name->Log.d(TAG, "greaterThan300Java8: " + name));  
}

我们运行这段代码会打印出:greaterThan300Java8: salmon

如果我们需要获得卡路里小于300的菜肴的名字列表,利用Java 8我们要如何做呢?

List<String> menuNames = menu.stream()  
		.filter(dish -> dish.getCalories() > 300)  
		.map(Dish::getName)  
		.collect(Collectors.toList());

是不是非常简单?如果我们不用Java 8实现的话,就只能够使用嵌套if else的形式来实现了,这种实现方式会大大降低我们的代码可读性,并且比较容易出错。而使用Java 8由于我们没有保存任何中间变量,所以出错的可能性会更低。

在上面的代码中,filter和map操作符属于中间操作符,中间操作符是指对流进行操作后返回的对象是一个流;而forEach和collect是终端操作符,终端操作符是指返回对象不是流的操作。它们的共同点是接受的对象都是一个Lambda表达式(行为参数化)。Java 8提供了多个流操作符来供我们使用。

Java 8常用的函数式接口

函数式接口就是指只含有一个抽象方法的接口,而抽象方法的签名我们一般称为函数描述符。一般被设计为函数式接口的接口会用@FunctionalInterface标注标记,当然这不是强制实现的,但是通常使用这一标注是比较好的做法。

较为常用的函数式接口:

Predicate

Predicate 接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean,在需要表示一个设计类型T的布尔表达式时,就可以使用这个接口。

Consumer

Consumer 定义了一个名叫accept的抽象方法,它接受泛型T对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。

Function

Function 接口定义了一个叫作apply的方法,它接受一个泛型T对象,并返回一个泛型R对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。

题外话:熟悉RxJava 2.0的同学应该不难看出,这些函数式接口的定义和RxJava 2.0中的十分类似,笔者推断RxJava 2.0中的一些标准应该是遵循Java 8标准的。

笔者整理了一个Java 8中的函数式接口表格供大家参考:

写给Android开发者的Java 8简单入门教程

这里需要注意的一点是,原始类型特化。我们知道,普通类型如:int,double等是无法使用泛型的。所以我们需要用它们的装箱类来使用泛型,如Integer,Double。装箱使用泛型会带来一个问题就是运行效率的问题,装箱类的自动装箱和拆箱会大大影响程序的运行效率。所以在Java 8中提供了一系列特化类型的类和接口等,以消除自动装箱盒拆箱带来的的影响。

Java 8中常用的流操作符

上面的例子中,我们简单的介绍过流的操作符。在这里笔者整理了一个关于流的操作符的表格,方便读者使用的时候用来参考。

写给Android开发者的Java 8简单入门教程

小结

从这篇文章可以得出一个结论:Java 8除了使我们编程变得更加简单外,还大大加强了代码的可读性。而对作为开发者的我们来说,使用Java 8能够简化代码的同时让我们专注于逻辑而不必写一堆模版代码。

这篇文章只是Java 8的简单的介绍文章,实际上Java 8提供的功能更加强大,有兴趣的读者可以继续深入了解下。大家也可以关注我的博客,我会不时发表一些关于Java 8文章的。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective Modern C++ 简体中文版

Effective Modern C++ 简体中文版

Scott Meyers / 高博 / 中国电力出版社 / 2018-4-23 / 99

想要彻底理解C++11和C++14,不可止步于熟悉它们引入的语言特性(例如,auto型别推导、移动语义、lambda表达式以及并发支持)。挑战在于高效地运用这些特性——从而使你的软件具备正确性、高效率、可维护性和可移植性。这正是这本实用的图书意欲达成的定位。它描述的正是使用C++11和C++14——现代C++来撰写真正卓越的软件之道。 涵盖以下主题: 大括号初始化、noexcept规格......一起来看看 《Effective Modern C++ 简体中文版》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具