Xiaolong的个人博客

每一天都值得被认真对待


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 爱过的人

Java泛型

发表于 2016-08-07 | 分类于 Java基础
闲谈:

说到泛型,大部分人肯定都不陌生,在我学习泛型之前,对他的概念是一个T类型的对象,它可以指代各种类型。没错,可以这么理解,但是其中又有很多值得思考的问题,泛型跟Object有什么区别,泛型的用法等等,这些都是值得我们去深思的一个问题。
以前一直都想着利用泛型来好好装逼,但是因为技术不足,也可以说是人懒,对java的理解没有这么深刻,学习的过程中总觉得特别困难,一直都处于一知半解的状态,最近在公司项目开发过程中,发现框架里面有好多东西都运用了泛型优化代码,感觉也差不多是再对泛型进一步学习的时候了。

说一说我印象最深刻的几行代码,是用来简化Android控件绑定的。

1
2
3
4
protect (V extends View) myFindView(int resId)
{
return (V)findViewById(resId);
}

这个是写在了基类的Activity中作为所有Activity的父类,在子类中findViewById的写法就变成了如下:

1
TextView mTextView = myFindView(R.id.mTextView);

对比android原生自带的findViewById是不是直接取消了类型的强转,这如果在Activity很多的情况下是可以少很多代码和时间的。
直接取消掉了类型的强转,是不是简单了许多。好了,这么一个强大的东西,是时候好好学习一波了。

一、泛型的概念

泛型实现了参数化类型的概念,使代码可以应用于多种类型。在你创建参数化类型的一个实例时,编译器会为你负责转型操作,并且保证类型的正确性。

二、泛型的使用

普通泛型

有许多原因促成了泛型的出现,而最引人注目的一个原因就是为了创造容器类。基本上所有的程序运行时都要求你持有一大堆对象,所以容器类算得上最具重用性的类库之一。
接下来我们可以通过对比来裂解一下泛型作为容器类的好处

1
2
3
4
5
6
7
8
9
10
11
12
13
class Model{}
public class Holder1 {
private AModel mAModel;
public Holder1(AModel AModel)
{
this.mAModel=AModel;
}
AModel get()
{
return mAModel;
}
}

这个类可以明确指定其持有的对象类型,但他的重用性就不怎么样了。试想一下如果我们也有一个BModel同样需要放在这样一个容器持有类中。再重新写一个类代码就显得太多太杂了,毕竟要实现的功能是差不多的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Holder2 {
private Object mModel;
public Holder2(Object model)
{
this.mModel=model;
}
Object get()
{
return mModel;
}
public static void main(String[] args)
{
Holder2 h2 = new Holder2(new Model());
Model model = (Model) h2.get();
}
}

现在Holder 可以存放各种不同类型的对象了,因为所有的java类都是Object的子类,java支持向上向下转型的。所以这个代码可以作为任意类型的容器了。
有些情况下,我们确实希望容器能够同时持有多种类型的对象,但是,通常而言,我们只会使用容器来存储一种类型的对象。泛型的主要目前之一就是用来指定容器要持有什么类型的对象,而由编译器来保证类型的正确性。
因此,与其使用Object,我们更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Holder3<T> {
private T mModel;
public Holder3(T model)
{
this.mModel=model;
}
T get()
{
return mModel;
}
public static void main(String[] args)
{
Holder3<Model> h3 = new Holder3(new Model());
Model model = h3.get();
}
}

现在,当你创建Holder3对象时必须指明想持有什么类型的对象,将其置于尖括号内。然后你就能在Holder3存入该类型。
并且,在你从Holder3取出它的持有对象时,自动就是正确的类型。
现在我们来试试元组,持有多种不同类型。

1
2
3
4
5
6
7
8
9
10
public class TwoTuple<A,B>
{
public final A first;
public final B second;
public TwoTuple(A a,B b)
{
first=a;
second=b;
}
}

现在我们拥有了一个可以持有两个对象的元组了,如果我们再需要三个对象的元组。

1
2
3
4
5
6
7
8
9
public class ThreeTuple(A,B,C) exrebds TwoTuple<A,B>
{
public final C third;
public ThreeTuple(A a,B b,C c)
{
super(a,b)
third=c;
}
}

小结
不知道有没有人注意到 Holder3<Model> h3 = new Holder3(new Model());在初始化这个对象持有类的时候我们指明了我们想要的类型。然后编译器自动帮我们编译成了这个类型。也就是说编译器在执行h3.get();的时候它是已经明确知道了h3.get()的类型了,并自动进行了强转。而Model model = (Model) h2.get();这个,当编译器在执行这一句的时候他还不知道h2.get()到底是什么类型,最后我们告诉它,要强转成Model以适配model这个实例化对象,来进行存储。

接下来讲讲泛型接口

泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
};
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};
----------------------------------------------------------
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl implements Info<String>{ // 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(String var){
this.var = var ;
}
public String getVar(){
return this.var ;
}
};
public class GenericsDemo{
public static void main(String arsg[]){
Info i = null; // 声明接口对象
i = new InfoImpl("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};

泛型也可以应用于接口,例如生成器,作为工厂方法设计模式的一种应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public interface Generator<T> {//生成器接口
T next();
}
public class Coffee {//咖啡类
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}
class Latte extends Coffee{}
class Mocha extends Coffee{}
class Cappuccino extends Coffee{}
class Americano extends Coffee{}
class Breve extends Coffee{}
/**
* 咖啡生成器类,随机生成咖啡
* @author T157
*
*/
public class CoffeeGenerator implements Generator<Coffee>,Iterable<Coffee>{
private Class[] types = {Latte.class,Mocha.class,Cappuccino.class,Americano.class,Breve.class};
private static Random rand = new Random(47);
private int mSize;
public CoffeeGenerator(){};
public CoffeeGenerator(int size) {
mSize=size;
}
@Override
public Iterator<Coffee> iterator() {
// TODO Auto-generated method stub
return new CoffeeInterator();
}
@Override
public Coffee next() {
try {
return (Coffee)types[rand.nextInt(types.length)].newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
public class CoffeeInterator implements Iterator<Coffee>{
int count =mSize;
@Override
public boolean hasNext() {
return count>0;
}
@Override
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
public static void main(String[] args)
{
CoffeeGenerator gen = new CoffeeGenerator();
for(int i=0;i<5;i++)
{
System.out.println(gen.next());
}
for(Coffee c: new CoffeeGenerator(5))
{
System.out.println(c);
}
}
}

泛型方法

只需要在方法返回类型前加泛型参数就可以啦,看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Demo{
public <T> T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
public class GenericsDemo{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun("汤姆") ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};

通过泛型方法返回泛型类型实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Info<T>{ // 指定上限
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ;
Info<String> i2 = new Info<String>() ;
i1.setVar("HELLO") ; // 设置内容
i2.setVar("汤姆") ; // 设置内容
add(i1,i2) ;
}
public static <T> void add(Info<T> i1,Info<T> i2){
System.out.println(i1.getVar() + " " + i2.getVar()) ;
}
};

使用泛型统一传入的参数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Info<T extends Number>{ // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo{
public static void main(String args[]){
Info<Integer> i = fun(30) ;
System.out.println(i.getVar()) ;
}
public static <T extends Number> Info<T> fun(T param){//方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
Info<T> temp = new Info<T>() ; // 根据传入的数据类型实例化Info
temp.setVar(param) ; // 将传递的内容设置到Info对象的var属性之中
return temp ; // 返回实例化对象
}
};


学习完泛型我们可以知道,泛型并非只用一个T字幕来表达,它的命名风格基本为大写的英文字母,比如ABCDEFG... 泛型可以通过继承来定义上限。可以实现泛型的方法,泛型类,泛型接口,可以定义泛型数组泛型嵌套等一系列使用方法。

它跟Object的区别:是没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

参考自网易博客北漂的小羊
java编程思想第四版 泛型篇
By Xiaolong:You have a dream,you got to protect it!

未命名

发表于 2016-08-07

title: Java中的Lambda表达式的使用
date: 2016-06-20 22:52:55
tags:

- Lambda表达式

category:

- Java基础

RxJava学习第一篇,一个优美的表达式,Lambda表达式。

闲谈:

之前加入某个python群的时候群验证就要求用Lambda实现求和。
当时好像是这么写了 Lambda x,y:x+y.
把这个写成一个函数就可以重复利用了,例:f = Lambda x,y:x+y f(x,y)
当时有种发现新大陆的感觉,觉得python真是强大。
后来才发现java、C# …中也有Lambda表达式的概念
嗯哼,Lambda也是吊吊哒。

最近在学习响应式编程框架:Rxjava,扩展RxAndroid+Retrofit,还有一些大神们开发出来的Rxjava在Android中牛逼的框架。
RxJava其中有很多的Lambda表达式用来代码简化。感觉这种技能装逼又能减少代码量的神器真的是不可或缺啊,好了不多说废话现在先来入门一下Lambda表达式。

一、Lambda简介

Lambda 表达式 Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。
所有 Lambda 表达式都使用 Lambda 运算符 =>;,该运算符读为“goes to”。该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。Lambda 表达式 x => x * x 读作“x goes to x times x”。

Lambda表达式的本质只是一个”语法糖”,由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。本人建议不要乱用,因为这就和某些很高级的黑客写的代码一样,简洁,难懂,难以调试,维护人员想骂娘.
Lambda表达式是Java SE 8中一个重要的新特性。Lambda表达式允许你通过表达式来代替功能接口。 Lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,Lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。 在本文中,我们将从简单到复杂的示例中见认识Lambda表达式和stream的强悍。

二、为什么使用Lambda表达式?

我们为什么需要Lambda表达式
主要有三个原因:

1.更加紧凑的代码
比如Java中现有的匿名内部类以及监听器(listeners)和事件处理器(handlers)都显得很冗长
2.修改方法的能力(我个人理解为代码注入,或者有点类似JavaScript中传一个回调函数给另外一个函数)
比如Collection接口的contains方法,当且仅当传入的元素真正包含在集合中,才返回true。而假如我们想对一个字符串集合,传入一个字符串,只要这个字符串出现在集合中(忽略大小写)就返回true。
简单地说,我们想要的是传入“一些我们自己的代码”到已有的方法中,已有的方法将会执行我们传入的代码。Lambda表达式能很好地支持这点
3.更好地支持多核处理
例如,通过Java 8新增的Lambda表达式,我们可以很方便地并行操作大集合,充分发挥多核CPU的潜能。
并行处理函数如filter、map和reduce。

三、Lambda表达式的语法

基本语法:
(parameters) -> expression
或
(parameters) ->{ statements; }

下面是Java Lambda表达式的简单例子:

// 1. 不需要参数,返回值为 5
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

好了,是不是对这种表达方式很感兴趣,让我们来看看开发需要准备的开发环境吧。

  • JAVA8 (没有的话赶紧去装一个)
  • Eclipse 任意版本能运行java就行

现在,我们已经知道什么是Lambda表达式,并且做好了前期准备,让我们先从一些基本的例子开始。 在本节中,我们将看到Lambda表达式如何影响我们编码的方式。 假设有一个玩家List,程序员可以使用 for 语句 (“for 循环”)来遍历,在Java SE 8中可以转换为另一种形式:

String[] atp = {"Rafael Nadal", "Novak Djokovic",
       "Stanislas Wawrinka",
       "David Ferrer","Roger Federer",
       "Andy Murray","Tomas Berdych",
       "Juan Martin Del Potro"};
List<String> players =  Arrays.asList(atp);

// 以前的循环方式
for (String player : players) {
     System.out.print(player + "; ");
}

// 使用 Lambda 表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));

// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);

可以看到Lambda表达式可以将我们的代码缩减到一行。 另一个例子是在图形用户界面程序中,匿名类可以使用Lambda表达式来代替。

// 使用匿名内部类
btn.setOnAction(new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent event) {
              System.out.println("Hello World!"); 
          }
    });

// 或者使用 Lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));

下面是使用Lambdas 来实现 Runnable接口 的示例:


// 1.1使用匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
}).start();

// 1.2使用 Lambda expression
new Thread(() -> System.out.println("Hello world !")).start();

// 2.1使用匿名内部类
Runnable race1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
};

// 2.2使用 Lambda expression
Runnable race2 = () -> System.out.println("Hello world !");

// 直接调用 run 方法(没开新线程哦!)
race1.run();
race2.run();

Runnable 的 Lambda表达式,使用块格式,将五行代码转换成单行语句。 接下来,在下一节中我们将使用Lambdas对集合进行排序。

使用Lambdas排序集合

在Java中,Comparator 类被用来排序集合。 在下面的例子中,我们将根据球员的 name, surname, name 长度 以及最后一个字母。 和前面的示例一样,先使用匿名内部类来排序,然后再使用Lambda表达式精简我们的代码。
在第一个例子中,我们将根据name来排序list。 使用旧的方式,代码如下所示:

String[] players = {"Rafael Nadal", "Novak Djokovic", 
    "Stanislas Wawrinka", "David Ferrer",
    "Roger Federer", "Andy Murray",
    "Tomas Berdych", "Juan Martin Del Potro",
    "Richard Gasquet", "John Isner"};

// 1.1 使用匿名内部类根据 name 排序 players
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.compareTo(s2));
    }
});

使用lambdas,可以通过下面的代码实现同样的功能:

// 1.2 使用 lambda expression 排序 players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players, sortByName);

// 1.3 也可以采用如下形式:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));

其他的排序如下所示。 和上面的示例一样,代码分别通过匿名内部类和一些lambda表达式来实现Comparator :

// 1.1 使用匿名内部类根据 surname 排序 players
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
    }
});

// 1.2 使用 lambda expression 排序,根据 surname
Comparator<String> sortBySurname = (String s1, String s2) -> 
    ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(players, sortBySurname);

// 1.3 或者这样,怀疑原作者是不是想错了,括号好多...
Arrays.sort(players, (String s1, String s2) -> 
      ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) 
    );

// 2.1 使用匿名内部类根据 name lenght 排序 players
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.length() - s2.length());
    }
});

// 2.2 使用 lambda expression 排序,根据 name lenght
Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(players, sortByNameLenght);

// 2.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));

// 3.1 使用匿名内部类排序 players, 根据最后一个字母
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
    }
});

// 3.2 使用 lambda expression 排序,根据最后一个字母
Comparator<String> sortByLastLetter = 
    (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(players, sortByLastLetter);

// 3.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));

就是这样,简洁又直观。 在下一节中我们将探索更多lambdas的能力,并将其与 stream 结合起来使用。

使用Lambdas和Streams

Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。 在接下来的例子中,我们将探索lambdas和streams 能做什么。 我们创建了一个Person类并使用这个类来添加一些数据到list中,将用于进一步流操作。 Person 只是一个简单的MODEL类:

public class Person {

private String firstName, lastName, job, gender;
private int salary, age;

public Person(String firstName, String lastName, String job,
                String gender, int age, int salary)       {
          this.firstName = firstName;
          this.lastName = lastName;
          this.gender = gender;
          this.age = age;
          this.job = job;
          this.salary = salary;
}
// Getter and Setter 
// . . . . .
}

接下来,我们将创建两个list,都用来存放Person对象:


List<Person> javaProgrammers = new ArrayList<Person>() {
  {
    add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
    add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
    add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
    add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
    add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
    add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
    add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
    add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
    add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
    add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
  }
};

List<Person> phpProgrammers = new ArrayList<Person>() {
  {
    add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
    add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
    add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
    add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000));
    add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100));
    add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300));
    add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100));
    add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000));
    add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600));
    add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800));
  }
};

现在我们使用forEach方法来迭代输出上述列表:

System.out.println("所有程序员的姓名:");
javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

我们同样使用forEach方法,增加程序员的工资5%:

System.out.println("给程序员加薪 5% :");
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());

javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);

另一个有用的方法是过滤器filter() ,让我们显示月薪超过1400美元的PHP程序员:

System.out.println("下面是月薪超过 $1,400 的PHP程序员:")
phpProgrammers.stream()
          .filter((p) -> (p.getSalary() > 1400))
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

我们也可以定义过滤器,然后重用它们来执行其他操作:


// 定义 filters
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));

System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:");
phpProgrammers.stream()
          .filter(ageFilter)
          .filter(salaryFilter)
          .filter(genderFilter)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

// 重用filters
System.out.println("年龄大于 24岁的女性 Java programmers:");
javaProgrammers.stream()
          .filter(ageFilter)
          .filter(genderFilter)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

使用limit方法,可以限制结果集的个数:

System.out.println("最前面的3个 Java programmers:");
javaProgrammers.stream()
          .limit(3)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));


System.out.println("最前面的3个女性 Java programmers:");
javaProgrammers.stream()
          .filter(genderFilter)
          .limit(3)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

排序呢? 我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:

System.out.println("根据 name 排序,并显示前5个 Java programmers:");
List<Person> sortedJavaProgrammers = javaProgrammers
          .stream()
          .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
          .limit(5)
          .collect(toList());

sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

System.out.println("根据 salary 排序 Java programmers:");
sortedJavaProgrammers = javaProgrammers
          .stream()
          .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
          .collect( toList() );

sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法:

System.out.println("工资最低的 Java programmer:");
Person pers = javaProgrammers
          .stream()
          .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
          .get()

System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())

System.out.println("工资最高的 Java programmer:");
Person person = javaProgrammers
          .stream()
          .max((p, p2) -> (p.getSalary() - p2.getSalary()))
          .get()

System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())

上面的例子中我们已经看到 collect 方法是如何工作的。 结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:

System.out.println("将 PHP programmers 的 first name 拼接成字符串:");
String phpDevelopers = phpProgrammers
          .stream()
          .map(Person::getFirstName)
          .collect(joining(" ; ")); // 在进一步的操作中可以作为标记(token)   

System.out.println("将 Java programmers 的 first name 存放到 Set:");
Set<String> javaDevFirstName = javaProgrammers
          .stream()
          .map(Person::getFirstName)
          .collect(toSet());

System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
          .stream()
          .map(Person::getLastName)
          .collect(toCollection(TreeSet::new));

Streams 还可以是并行的(parallel)。 示例如下:

System.out.println("计算付给 Java programmers 的所有money:");
int totalSalary = javaProgrammers
          .parallelStream()
          .mapToInt(p -> p.getSalary())
          .sum();

我们可以使用summaryStatistics方法获得stream 中元素的各种汇总数据。 接下来,我们可以访问这些方法,比如getMax, getMin, getSum或getAverage:

//计算 count, min, max, sum, and average for numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers
          .stream()
          .mapToInt((x) -> x)
          .summaryStatistics();

System.out.println("List中最大的数字 : " + stats.getMax());
System.out.println("List中最小的数字 : " + stats.getMin());
System.out.println("所有数字的总和   : " + stats.getSum());
System.out.println("所有数字的平均值 : " + stats.getAverage());

OK,就是酱,就是这么吊,希望你喜欢!

总结

在本文中,我们学会了使用lambda表达式的不同方式,从基本的示例,到使用lambdas和streams的复杂示例。 此外,我们还学习了如何使用lambda表达式与Comparator 类来对Java集合进行排序。

By Xiaolong:没有伞的孩子必须努力奔跑!

java爬虫教程:模拟用户表单登录

发表于 2016-06-01 | 分类于 Java爬虫

这个是爬虫教程第三篇,教大家如何模拟用户表单登录。

前期准备:

  • JSOUP 1.83 jar包
  • Eclipse 任意版本能运行java就行
  • 谷歌浏览器
第一步:依然是分析页面结构

我们要模拟CSDN用户表单登录,来获取用户登录后的数据。
在一些网站和论坛,部分内容总是需要会员用户等需要一定权限的用户才能看得到。
好了由此可见模拟用户表单登录的重要性了。

进入登录页,在页面上按F12查看页面元素的内容。

这次我们只要form标签,和其内部的账号密码输入框信息。

第二步:讲解一下Post需要的操作具体流程

一般表单的id都是唯一的,所以要过滤出表单是很容易的一件事情
这里表单的id值是fm1

1
List<Element> et = d1.select("#fm1");// 获取form表单

下一步是获得表单下面的帐号和密码输入框控件,同样可以根据id来过滤,不过这里使用的是name属性。
一般网站设计都会把name属性配上value,post到服务端。
直接在Map对象中put帐号和密码的键值对,代码如下

1
2
Map<String, String> datas = new HashMap<>();
datas.put(e.attr("name"), e.attr("value"));

这个就是表单的帐号密码输入框的键值
也可以不做遍历直接

1
2
datas.put("username", "your username");
datas.put("password", "your password");

哈哈,是不是少了许多。
经过这一步之后datas里已经存放了我们要post的数据了。
可以执行第二次请求将登录信息post上去了。
Response login = con2.ignoreContentType(true).method(Method.POST)
.data(datas).cookies(rs.cookies()).execute();
携带登录的Map数据和第一次登录返回的cookie,进行post。

第三步:开始写代码

好了就是这么简单。接下来看看代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
/**
* 使用Jsoup模拟登陆CSDN
*
*
* 大体思路如下:
*
* 第一次请求登陆页面,获取页面信息,包含表单信息,和cookie(这个很重要),拿不到,会模拟登陆不上
*
*
* 第二次登陆,设置用户名,密码,把第一次的cooking,放进去,即可
*
* 怎么确定是否登陆成功?
*
* 登陆后,打印页面,会看到账户的详细信息。
*
*
* @date 2016年6月13日
* @author xiaolong
*
*
* **/
public class LoginDemo {
public static void main(String[] args) throws Exception {
LoginDemo loginDemo = new LoginDemo();
loginDemo.login("your account", "password");// 输入CSDN的用户名,和密码
}
/**
* 模拟登陆CSDN
*
* @param userName
* 用户名
* @param pwd
* 密码
*
* **/
public void login(String userName, String pwd) throws Exception {
// 第一次请求
Connection con = Jsoup
.connect("https://passport.csdn.net/account/login");// 获取连接
con.header("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");// 配置模拟浏览器
Response rs = con.execute();// 获取响应
Document d1 = Jsoup.parse(rs.body());// 转换为Dom树
List<Element> et = d1.select("#fm1");// 获取form表单,可以通过查看页面源码代码得知
// 获取,cooking和表单属性,下面map存放post时的数据
Map<String, String> datas = new HashMap<>();
for (Element e : et.get(0).getAllElements()) {
if (e.attr("name").equals("username")) {
e.attr("value", userName);// 设置用户名
}
if (e.attr("name").equals("password")) {
e.attr("value", pwd); // 设置用户密码
}
if (e.attr("name").length() > 0) {// 排除空值表单属性
datas.put(e.attr("name"), e.attr("value"));
}
}
/**
* 第二次请求,post表单数据,以及cookie信息
*
* **/
Connection con2 = Jsoup
.connect("https://passport.csdn.net/account/login");
con2.header("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");
// 设置cookie和post上面的map数据
Response login = con2.ignoreContentType(true).method(Method.POST)
.data(datas).cookies(rs.cookies()).execute();
// 打印,登陆成功后的信息
System.out.println(login.body());
// 登陆成功后的cookie信息,可以保存到本地,以后登陆时,只需一次登陆即可
Map<String, String> map = login.cookies();
for (String s : map.keySet()) {
System.out.println(s + " " + map.get(s));
}
}
}

总结

这个类中写了两次网站访问的请求
第一次请求用来获取cookie信息
第二次请求将携带cookie和登录数据的信息post出去用来模拟登录。
就是这么简单~~~

小Tips:
想要模拟用户表单登录,链接头信息是不可少的,"User-Agent"代表的是浏览器访问信息。
通过下图可以看到请求头可以有这么多的信息,
服务端可能会通过约束请求头来判别用户post/get的信息是否合法
所以请求头很重要~请求头很重要~请求头很重要~(重要的事情说三遍)

这个是登录后每一次操作都需要携带的头部信息。可以通过F12查看页面网络访问状态来查看请求头和返回头。
好啦,有了模拟表单登录了,是时候去大展身手了。
爬虫教程到此结束,希望有兴趣的小伙伴可以继续深入研究

By Xiaolong:You have a dream,you got to protect it!

java爬虫教程:爬取CSDN博客文章

发表于 2016-05-08 | 分类于 Java爬虫

这个是爬虫教程第二篇,教大家如何爬取普通页面的内容。

前期准备:

  • JSOUP 1.83 jar包
  • Eclipse 任意版本能运行java就行
  • 谷歌浏览器
第一步:分析页面结构

我们要爬CSDN博客文章,直接访问某个人的CSDN博客主页,比如说我的CSDN主页:http://blog.csdn.net/guoxiaolongonly
进入主页,在页面上按F12查看标签的内容。

可以看到如上内容,基本的html就是js+css+html标签的构造,我们使用比较多的是a、img这两个元素下的内容。第一个是链接的元素,通常获取这个来实现往后的链接的跳转。第二个是图片元素,有时候需要爬图片链接来下载图片就爬这个Tag来过滤。里面的内容也都是一个链接地址。

第二步:选取合适的过滤方法

我现在想要爬的就是这个标题中的链接。

这个标签里href属性放了我想要访问的文章详情的链接

可以使用Jsoup的方法:Elements elements = doc.getElementsByTag("a");//找到所有a标签
对a标签进行过滤就行了
也可以直接获取class标签下的内容,再在这个class下找到a标签获取a标签的href属性值。
如:Element elementTitle = document.getElementsByClass("link_title").first();//标题。
这样只需要再找到这个elementTitle下的a元素就好了。

当然JSOUP过滤标签的方式很多,可以根据需要选择合适的方法来过滤。

第三步:开始写代码

好了就是这么简单。接下来看看代码吧。

//代码为2018年11月10日更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
public class MySearchTest {
private static String url = "https://blog.csdn.net";
private static String blogName = "guoxiaolongonly";
public static void main(String[] args) {
getArticleListFromUrl(url+"/" + blogName);
}
 /**
 * 获取文章列表
 *
 * @param listurl
 */
public static void getArticleListFromUrl(final String listurl) {
    boolean isStop = false;
    Document doc = null;
    try {
        doc = Jsoup.connect(listurl).userAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36").timeout(3000).post();
    } catch (IOException e) {
        e.printStackTrace();
    }
    Elements elements = doc.getElementsByTag("a");//找到所有a标签
    for (Element element : elements) {
        final String relHref = element.attr("href"); // == "/"这个是href的属性值,一般都是链接。这里放的是文章的连接
        //用if语句过滤掉不是文章链接的内容。因为文章的链接有两个,但评论的链接只有一个,反正指向相同的页面就拿评论的链接来用吧
        if (!relHref.startsWith("http://") && relHref.contains("details") && relHref.endsWith("comments")) {
            StringBuffer sb = new StringBuffer();
            sb.append(relHref);
            System.out.println(sb.substring(0, sb.length() - 9));//去掉最后的#comment输出
            getArticleFromUrl(sb.substring(0, sb.length() - 9));//可以通过这个url获取文章了
        }
        if (relHref.equals("https://mp.csdn.net//postlist")) {
            isStop = true;
        }
    }
    if (!isStop) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (!listurl.contains("list")) {
                    getArticleListFromUrl(listurl + "/article/list/1");//获取下一页的列表
                } else {
                    getArticleListFromUrl(listurl.substring(0, listurl.length() - 1) +
                            (Integer.valueOf(listurl.substring(listurl.length() - 1, listurl.length())) + 1));//获取下一页的列表
                }
            }
        }).start();
    }
}
/**
* 获取文章内容
* @param detailurl
*/
public static void getArticleFromUrl(String detailurl) {
try {
Document document = Jsoup.connect(detailurl).userAgent("Mozilla/5.0").timeout(3000).post();
Element elementTitle = document.getElementsByClass("link_title").first();//标题。 这边根据class的内容来过滤
System.out.println(elementTitle.text());
String filename = elementTitle.text().replaceAll("/", "或");
Element elementContent = document.getElementsByClass("article_content").first();//内容。
saveArticle(filename , elementContent.text(), blogName);
// String Content =elementContent.te xt().replaceAll(" ", "\t");
// System.out.println(elementContent.text()+"\n");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 保存文章到本地
* @param titile
* @param content
* @param blogName
*/
public static void saveArticle(String titile, String content, String blogName) {
String lujing = "d:\\MyLoadArticle\\" + blogName + "\\" + titile + ".txt";//保存到本地的路径和文件名
File file = new File(lujing);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
bw.flush();
bw.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
​

总结

这个类中我封装了获取文章列表、获取文章内容保存文章内容的方法。
首先解析首页获取首列表页中每个文章的链接,再依次访问每个链接爬取文章标题和内容。
如果列表页存在下一页。再对下一页列表进行相同的操作,是不是很好理解了。
文章保存格式为txt文本,保存的路径根据博客名来命名,保存的文件名称为文章标题,文本则保存文章内容。
有这个demo是不是可以写出文章搜索器啊,文章过滤器...甚至爬取整个csdnblog的所有文章都不是问题。就看你方法和过滤怎么做了。
大家可以设置blogName去爬你想要的文章了,比如说我:guoxiaolongonly
这边用post模拟浏览器请求。因为直接get,页面无法加载。
针对文本保存文档编码乱码问题。还希望大家自己研究一下。
好运~~ 晚安~

By Xiaolong:You have a dream,you got to protect it!

java爬虫教程:JSOUP

发表于 2016-05-06 | 分类于 Java爬虫

爬虫教程第一篇,认识强大的java包JSOUP。
接下来我来介绍一下Java爬虫,(ˉ﹃ˉ)我大Java依然这么强大,谁还敢鄙视我大Java~

嗯哼,刚刚说Java强大,其实Java强大在它的第三方库。每一个jar包都是某个Java开发者的劳动成果,当然每一个Java开发者都应该自豪,因为正是我们的付出让Java日益强大。接下来要讲的是一个非常强大的工具包JSOUP,java爬虫实现基本依赖于它!

一、Jsoup简介

在以往用java来处理解析HTML文档或者片段时,我们通常会采用htmlparser这个开源类库。现在我们有了JSOUP,以后的处理HTML的内容只需要使用JSOUP就已经足够了,JSOUP有更快的更新,更方便的API等。
jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据,可以看作是java版的jQuery。
jsoup的主要功能如下:
从一个URL,文件或字符串中解析HTML;
使用DOM或CSS选择器来查找、取出数据;
可操作HTML元素、属性、文本;
jsoup是基于MIT协议发布的,可放心使用于商业项目。官方网站:http://jsoup.org/

二、解析遍历HTML文档

Jsoup处理HTML文件是,是将用户输入的HTML文档,解析转换成一个Document对象进行处理。Jsoup一般支持以下几种来源内容的转换。

1. 解析一个html字符串
2. 解析一个body片段
3. 根据一个url地址加载Document对象
4. 根据一个文件加载Document对象

1.解析一个html字符串

在处理一个html字符串。我们可能需要对其进行解析,并提取其内容,或校验其格式是否完整,或者想修改它。Jsoup可以帮助我们轻松的解决这些问题。
在Jsoup中有一个这样的静态方法Jsoup.parse(String html),可以将我们的html片段转换成Document对象。示例如下:

1
2
String html = "<div><p align=\"center\"><img alt=\"\" width=\"660\" height=\"852\" src=\"/erp/UserFiles/Image/51.jpg\" />这是P元素的内容</p>";
Document document = Jsoup.parse(html);

使用上面的方法,就可以将html字符串,转换成Document对象,一旦有了Document对象,我们就可以使用其中适当的方法根据需求处理问题。我们可以看到这里转换的html片段并不是一个合法的html片段,里面的div标签没有闭合。这对于Jsoup来说不是问题,它可以很好的处理这类问题。

2.解析body片段

假如我们现在有一个HTML片断 (比如. 一个 div 包含一对 p 标签; 一个不完整的HTML文档) 想对它进行解析。这个HTML片断可以是用户提交的一条评论或在一个CMS页面中编辑body部分。我们可以使用使用Jsoup.parseBodyFragment(String html) 方法。
示例如下:

1
2
String html = "<div><p align=\"center\"><img alt=\"\" width=\"660\" height=\"852\" src=\"/erp/UserFiles/Image/51.jpg\" />这是P元素的内容</p>";
Document document = Jsoup.parseBodyFragment(html);

看到这里可能会有疑问,这个和上面的html片段是一样的嘛。没错是一样的,parseBodyFragment 方法创建一个空壳的文档,并插入解析过的HTML到body元素中。假如使用正常的 Jsoup.parse(String html) 方法,通常也可以得到相同的结果,但是明确将用户输入作为 body片段处理,以确保用户所提供的任何糟糕的HTML都将被解析成body元素。

Document.body() 方法能够取得文档body元素的所有子元素,与doc.getElementsByTag(“body”)相同。

3.根据一个URL地址加载Document对象

有时候我们可能希望通过一个url地址,然后提取里面的内容,转换成document对象。我们以前可能是使用http client等模拟一个请求,然后取得返回内容等,使用Jsoup方便简单的解决这个问题。示例如下:

1
2
3
Document document = Jsoup.connect("http://www.baidu.com").get();
String title = document.title();
String text = document.text();

connect(String url) 方法创建一个新的 Connection, 和 get() 取得和解析一个HTML文件。如果从该URL获取HTML时发生错误,便会抛出 IOException,应适当处理。
Connection 接口还提供一个方法链来解决特殊请求,具体如下:

1
Document doc = Jsoup.connect("http://test.com").data("query", "Java").userAgent("Mozilla").cookie("auth", "token").timeout(3000).post();

可以向链接地址post参数,设置userAgent,cookie,timeout等,而且这里是采用的链接操作很方便(熟悉jQuery的应该很熟悉这样的链接操作)。

4.根据文件加载document

有时候我们要处理的html内容,可能是存在硬盘上的某个文件里面,我们需要从中提取或者解析某些内容出来,我们可以通过Jsoup来这样处理。示例代码如下:

1
2
File input = new File("d:/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://test.com/");

看到这里可能有一个疑问,第一个参数是文件,第二是编码,第三个是什么呢?第三个参数是baseUrl,使用他我们可以方便的处理相对路径问题,如果不需要可以不传,这是一个多态方法,在前面的三个部分里面,都可以再加一个这样的baseUrl,后面会详细讲述。

三、 数据抽取

1.使用Dom方法遍历文档

通过第二章我们可以获取一个document的对象,我们可以通过这个对象来遍历文档,如:

1
2
3
4
5
6
7
Document doc = Jsoup.parse(input, "UTF-8", "http://test.com/");
Element content = doc.getElementById("content");
Elements links = content.getElementsByTag("a");
for(Element link : links) {
String linkHref = link.attr("href");
String linkText = link.text();
}

这里我们可以方便的使用Doument对象的方法来获取内容。常用方法如下:

查找元素
  • getElementById(String id)
  • getElementsByTag(String tag)
  • getElementsByClass(String className)
  • getElementsByAttribute(String key)(and related methods)
  • Element siblings: siblingElements(), firstElementSibling(), lastElementSibling();nextElementSibling(), previousElementSibling()
  • Graph: parent(), children()), child(int index)
元素数据
  • attr(String key)获取属性attr(String key, String value)设置属性
  • attributes()获取所有属性
  • id(), className() and classNames()
  • text()获取文本内容text(String value) 设置文本内容
  • html()获取元素内HTMLhtml(String value)设置元素内的HTML内容
  • outerHtml()获取元素外HTML内容
  • data()获取数据内容(例如:script和style标签)
  • tag() and tagName()
操作HTML和文本
  • append(String html), prepend(String html)
  • appendText(String text), prependText(String text)
  • appendElement(String tagName), prependElement(String tagName)
  • html(String value)

2.使用选择器来查找元素

使用jQuery时,我们无不为其强大的选择器叹服,jsoup有同样的强大的选择器,可以方便我们的对文档进行处理。示例代码如下:
jsoup elements对象支持类似于CSS (或jquery)的选择器语法,来实现非常强大和灵活的查找功能。.
这个select 方法在Document, Element,或Elements对象中都可以使用。且是上下文相关的,因此可实现指定元素的过滤,或者链式选择访问。
Select方法将返回一个Elements集合,并提供一组方法来抽取和处理结果。

1
2
3
4
5
6
Elements links = doc.select("a[href]"); //带有<span style="text-decoration: underline;">href</span>属性的a元素
Elements pngs = doc.select("img[src$=.png]");
//扩展名为.<span style="text-decoration: underline;">png</span>的图片
Element masthead = doc.select("div.masthead").first();
//class等于<span style="text-decoration: underline;">masthead</span>的<span style="text-decoration: underline;">div</span>标签
Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素

jsoup elements对象支持类似于CSS (或jquery)的选择器语法,来实现非常强大和灵活的查找功能。.
这个select 方法在Document, Element,或Elements对象中都可以使用。且是上下文相关的,因此可实现指定元素的过滤,或者链式选择访问。
Select方法将返回一个Elements集合,并提供一组方法来抽取和处理结果。

3.从元素中抽取属性和文档

  • 使用Jsoup抽取属性,一般方法如下:
  • 要取得一个属性的值,可以使用Node.attr(String key) 方法
  • 对于一个元素中的文本,可以使用Element.text()方法
  • 对于要取得元素或属性中的HTML内容,可以使用Element.html(), 或 Node.outerHtml()方法
    示例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    String html = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>";
    Document doc = Jsoup.parse(html);//解析HTML字符串返回一个Document实现
    Element link = doc.select("a").first();//查找第一个a元素</pre>
    String text = doc.body().text(); // "An example link"//取得字符串中的文本
    String linkHref = link.attr("href"); // "http://example.com/"//取得链接地址
    String linkText = link.text(); // "example""//取得链接地址中的文本</pre>
    String linkOuterH = link.outerHtml();
    // "<a href="http://example.com"><b>example</b></a>"
    String linkInnerH = link.html(); // "<b>example</b>"//取得链接内的html内容

4.URL处理

我们在处理HTML内容时,可能经常会遇到这种问题,需要将html页面里面的链接地址从相对地址转换成绝对地址,jsoup有一个方法用来解决此问题。我们前面对到的baseurl,就是用来解决此问题的。示例代码如下:

1
2
3
4
5
Document doc = Jsoup.connect("http://www.baidu.com/").get();
Element link = doc.select("a").first();
String relHref = link.attr("href"); // == "/"
String absHref = link.attr("abs:href");
// "http://www.baidu.com/gaoji/preferences.html"

在HTML元素中,URLs经常写成相对于文档位置的相对路径: …. 当你使用 Node.attr(String key) 方法来取得a元素的href属性时,它将直接返回在HTML源码中指定定的值。
假如你需要取得一个绝对路径,需要在属性名前加 abs: 前缀。这样就可以返回包含根路径的URL地址attr(“abs:href”)
因此,在解析HTML文档时,定义base URI非常重要。如果你不想使用abs: 前缀,还有一个方法能够实现同样的功能 Node.absUrl(String key)。

数据修改

1.设置属性值

在处理html时,我们有时候可能需要修改里面的属性值,如图片地址、class名称等各种属性。
可以使用属性设置方法 Element.attr(String key, String value), 和 Elements.attr(String key, String value).
假如你需要修改一个元素的 class 属性,可以使用 Element.addClass(String className) 和Element.removeClass(String className) 方法。
Elements 提供了批量操作元素属性和class的方法,比如:要为div中的每一个a元素都添加一个rel=”nofollow”
可以使用如下方法:

1
doc.select("div.comments a").attr("rel", "nofollow");

这里的jsoup方法同样支持链接操作,如下:

1
doc.select("div.masthead").attr("title", "jsoup").addClass("round-box");

2.设置元素的html内容

我们需要向html里面添加html片段等内容时可以如下操作:

1
2
3
4
5
6
7
8
9
Element div = doc.select("div").first(); // <div></div>
div.html("<p>lorem ipsum</p>"); // <div><p>lorem ipsum</p></div>
div.prepend("<p>First</p>");//在div前添加html内容
div.append("<p>Last</p>");//在div之后添加html内容
// 添完后的结果: <div><p>First</p><p>lorem ipsum</p><p>Last</p></div>
Element span = doc.select("span").first(); // <span>One</span>
span.wrap("<li><a href='http://example.com/'></a></li>");
//对元素包裹一个外部HTML内容添完后的结果:
//<li><a href="http://example.com"><span>One</span></a></li>

3.设置元素的文本内容

如果我们需要修改元素内的文本内容,可以如下操作:

1
2
3
4
5
Element div = doc.select("div").first(); // <div></div>
div.text("five > four"); // <div>five &gt; four</div>
div.prepend("First ");
div.append(" Last");
// now: <div>First five &gt; four Last</div>

说明

文本设置方法与 HTML setter 方法一样:
Element.text(String text) 将清除一个元素中的内部HTML内容,然后提供的文本进行代替
Element.prepend(String first) 和 Element.append(String last) 将分别在元素的内部html前后添加文本节点。
对于传入的文本如果含有像 <, > 等这样的字符,将以文本处理,而非HTML。

By Xiaolong:You have a dream,you got to protect it!

Welcome To My Personal Blog

发表于 2016-03-07

说说感想

经过几个小时的努力,我的个人博客终于架设完了。之前一直想要有一个个人博客,可以把自己学习的资源,自己的体会分享出来。现在终于可以实现啦~~<( ̄︶ ̄)>

虽然现在很多平台类似于CSDN、博客园、甚至新浪博客,都已经很普遍了,而我更期待的是有一个个人管理的博客站点,这才是最好玩的一件事情。<(‵□′)/~

之前学过java想直接用javaee写一个自己的站点,但是需要涉及的技术太多,还需要云服务器来存放服务器后台文件。过程特别繁琐~~嗯,特别麻烦~~反正我是懒得再自己去写框架找界面样式来搭建我的博客(ˉ﹃ˉ)~~

现在感谢github和hexo让我有这样的机会,将博客绑定域名,实现真正意义上的绝对权限的本地管理~

讲一下原理

这个模式类似于GitHub 提供服务器和空间。用git 配合Hexo来管理本地文档。然后通过域名解析把自己买的域名指向,gitHub提供的的ip。再由gitHub来转换该域名对应访问的站点。

优点就是:不用自己购买服务器,架设服务器。不用自己写博客后端框架,更不用写前端样式啦,都是可以直接拿模版来操作的( ̄︶ ̄)↗ 哈哈哈哈~~。

有兴趣的小伙伴赶紧学起来~

学架设:点击这里

小Tips

如果你是一名学生开发者,推荐你看看这个:腾讯云云加校园计划

你将会获得一个免费的域名,和一个一块钱/月的云服务器。可以去做好多好玩的事情啦。

By Xiaolong

1…34
小龙

小龙

程序猿

36 日志
14 分类
23 标签
GitHub CSDN
© 2019 小龙
由 Hexo 强力驱动
主题 - NexT.Pisces