Java学习笔记

编程是思想,是用抽象的方法解决科学的问题。

面向对象

目的:

  • 代码重用
  • 接口重用

封装


封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据,数据操作,方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

  • 问题域中该有哪些类,哪些对象
  • 每个类,每个对象中该具有什么属性,什么方法,什么参数
  • 类与类之间是什么关系
  • 封装类的考虑:名词
  • 定义属性的考虑:不脱离具体的应用环境(不建议直接访问某各类中的属性,可以定义为privete)
  • 方法的封装:站在主语的角度看待问题,描述问题和解决问题
  • 如通过set/get对类的属性进行操作,降低耦合对
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
public class DataBean {
	private int year;
	private int month;
	private int day;

	public int getYear() {
		return year;
	}

	public void setYear(int year) {
		this.year = year;
	}

	public int getMonth() {
		return month;
	}

	public void setMonth(int month) {
		this.month = month;
	}

	public int getDay() {
		return day;
	}

	public void setDay(int day) {
		this.day = day;
	}
}

Java的四种访问控制符

  • default:默认的,在同一包内可见,不使用任何修饰符。
  • private:私有的,在同一类内可见。
  • public:共有的,对所有类可见。
  • protected:受保护的,对同一包内的类和所有子类可见。

继承


它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//基类、父类、超类
public class Animal {

	public void eat() {
	}

    public void sleep() {
	}
}
//“子类、派生类
public class Dog extends Animal {

    public void go(){
    }

    @Override
    public void eat(){
        
    }
}

继承的3种实现方式

  • 实现继承是指使用基类的属性和方法而无需额外编码的能力;
  • 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
  • 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

多态


所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口

多态的3个特征

  • 有继承,实现接口类是继承的一种
  • 有重写,重写父类(抽象类)中的方法
  • 有父类的引用指向子类的对象

抽象类与接口类的选择

  • 没有具体事物作为参考对象,或者事物种类繁多就设计为抽象类
  • 如果考虑一类事物或者几类事物之间的共同特征和相同属性,就设计为接口类

抽象类详解:

  • 含有抽象方法的类必须是抽象类,抽象类必须被继承,抽象方法必须被重写,或则重新声明定义,或则将子类也定义成抽象类
  • 抽象类不能被实例化,也就是不能创建对象
  • abstract 抽象方法:只需要声明也不需要实现
  • 子类重写实现抽象方法(抽象方法一定要被重写,或者再定义声明一遍)

多态的优点

  • 每个Java类只能单继承(类/抽象类)多实现(接口)
  • 可扩展性(Extensibility)
  • 多个无关的类可以实现同一个接口
  • 一个类可以实现多个无关的接口

重载与重写

  • 方法重载(overload)实现的是编译时的多态性(也称为前绑定)
  • 方法重写(override)实现的是运行时的多态性(也称为后绑定)。
  • 覆盖(重写),是指子类重新定义父类的虚函数的做法。
  • 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
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
// 历
public interface Almanac{
    public String getDate();
}
// 农历
public class LunarImpl implements Almanac{
    @Override
	public String getDate() {
		return TimeUtil.dateFormat(calendar, "yyyy年MM月dd日");
	}
}
// 伊斯兰历
public class IslamicImpl implements Almanac{
    @Override
	public String getDate() {
		return getIslamicYear() + "年" + getIslamicMonth() + "月" + getIslamicDay() + "日";
	}
}
public class Main{

    private static void printDate(Almanac almanac){
        System.out.println(almanac.getDate());
    }

    public static void main(String[] args) {
        printDate(new IslamicImpl());
        // 或者
        printDate(new LunarImpl());
    }
}

静态代码块


1
2
3
4
5
6
public class Test{
    Map<String, String> map = new LinkedHashMap<String, String>();
    static{
        map.put("Test", "static");
    }
}
  • 类被加载后,只执行一次
  • 可用户数据初始化,Map,List等

动态代码块


1
2
3
4
5
6
public class Test{
    Map<String, String> map = new LinkedHashMap<String, String>();
    {
        map.put("Test", "static");
    }
}
  • 对象被创建一次(new()) 就执行一次
  • 无条件加载在构造方法的前面,无论构造方法有无参数

类之间的关系

  1. 继承关系:子类继承父类
  2. 实现关系:实现某一个接口类
  3. 依赖关系:方法的参数是类的引用
  4. 关联关系:两个类项目各有另外一方的引用
  5. 聚合关系:一个类拥有另一个类的引用
  6. 组合关系:一个类拥有另一个类的对象,对象必须被初始化,可以构造方法中初始化对象
  7. 耦合强度:继承>实现>组合>聚合>关联>依赖

关键字之instanceof

  • instanceof 是 Java 的一个二元操作符,类似于 ==,>,< 等操作符。
  • instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.Vector;
 
public class Main {
 
public static void main(String[] args) {
   test(new ArrayList());
}
public static void test(Object o) {
      if (o instanceof Vector)
      System.out.println("对象是 java.util.Vector 类的实例");
      else if (o instanceof ArrayList)
      System.out.println("对象是 java.util.ArrayList 类的实例");
      else
      System.out.println("对象是 " + o.getClass() + " 类的实例");
   }
}

可变参数

什么叫可变参数

方法的参数可以是0个到多个的Object类型或者Object[]类型。

调用原则

如果有参数直接对应的方法,优先调用该方法,否则调用可变参数的方法。

示例一

1
2
3
public TimeBean(String... str) {
	this(str[0], str[1], TimeUtil.strToCalendar(str[2] + " " + str[3]));
};

示例一的调用

第一种方法

1
new TimeBean("广东省", "徐闻县","2018-09-07","18:24:54");

第二种方法

1
2
String[] strs = {"广东省", "徐闻县","2018-09-07","18:24:54"};
TimeBean timeBean = new TimeBean(strs);

示例二

1
2
3
public TimeBean(String[]... strs ) {
	this(strs[0][0], strs[0][1], TimeUtil.strToCalendar(strs[0][2] + " " + strs[0][3]));
}

示例二的调用

1
2
String[][] strs = { { "广东省", "徐闻县", "2018-09-07", "18:24:54" }, };
TimeBean timeBean = new TimeBean(strs);

领域模型

  • 数据对象:xxxDO,xxx即为数据表名。
  • 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
  • 展示对象:xxxVO,xxx一般为网页名称。
  • POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。

分层领域模型

PO

Persistant Object 持久对象

与数据库SQL结果O/R映射的Java类对象,可以是一个表,也可以是多个表。

DO

Data Objec 数据对象

与数据库表字段一一对应的Java实体类,一般服务于DAO层,是MyBatis的映射实体类(Entity)。

BO

Business Object 业务对象

将一个业务抽象成一个具体的模型,其中可以包含多个PO,DO等。

AO

Application Object 应用对象

在Web层与Service层之间抽象的复用对象模型。

VO

View Object 展示对象

将前端页面或者某项数据抽象成一个对象模型。

DTO

Data Transfer Object 数据传输对象

层与层之间,请求与响应之间传递的数据模型。

泛型

通配符是什么

<?>
指的是Object及其子类,也就是所有Java对象

泛型是什么

  1. <E>:Element (容器中的元素,如集合)
  2. <T> :Type(Java 类)
  3. <K>:Key(键)
  4. <V>:Value(值)
  5. <N> :Number(数值类型)
  6. <S>、<U><A><B> ….

声明泛型

<T>
声明一个泛型类或者泛型方法
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ResultDTO<T>  implements Serializable{
    private static final long serialVersionUID = 801125677591974439L;
	private int code;
	private String msg;
    @JsonInclude(JsonInclude.Include.NON_DEFAULT)
	private Object data;
	
	private ResultDTO(ResultEnum stateEnum, Object data) {
		this.code = stateEnum.getCode();
		this.msg = stateEnum.getMsg();
		this.data = data;
	};
    //setter
    //getter
}

使用泛型

<?>
使用一个泛型类或者泛型方法
示例:

1
2
3
4
5
6
7
8
public static ResultDTO<?> entity(int code,Object... data) {
    ResultEnum resultEnum = ResultEnum.getEnum(code);
	if(0==data.length) {
		return new ResultDTO<ResultEnum>(resultEnum);
	}else{
		return new ResultDTO<Object>(resultEnum,data);
	}
}

通配符之边界

为什么通配符要加边界

为了解决容器中存储的元素不能是该基类(父类)的子类

上界通配符

关键字:extends
禁止做插入操作,只做读取
最大只能是该基类父类)的子类

1
2
3
4
5
6
7
8
public static ResultDTO<? extends Object> entity1(int code,Object... data) {
	ResultEnum resultEnum = ResultEnum.getEnum(code);
	if(0==data.length) {
		return new ResultDTO<ResultEnum>(resultEnum);
	}else{
		return new ResultDTO<Object>(resultEnum,data);
	}
}

下界通配符

关键字:super
最小只能是该子类,不能是该类的子类
无法确定存入类型,可以插入操作,而无法读取操作
Apple 继承 FruitFruit 继承 Food

1
2
3
4
5
6
7
8
9
10
public static ResultDTO<? super Fruit> entity(int code,Object... data) {
	ResultEnum resultEnum = ResultEnum.getEnum(code);
	if(0==data.length) {
		return new ResultDTO<Food>(resultEnum);
	}if(1==data.length) {
		return new ResultDTO<Fruit>(resultEnum);
	}else{
		return new ResultDTO<Apple>(resultEnum,data);//报错
	}
}

报错

Type mismatch: cannot convert from ResultDTO<Apple> to ResultDTO<? super Fruit>

PECS原则

Producer Extends Consumer Super原则

  1. extends 推荐读操作
  2. super 推荐写操作

  1. 声明泛型不能用无边界通配符<?>
  2. 上界<? extends T>不能往里存,只能往外取
  3. 下界<? super T>不影响往里存,但往外取只能放在Object对象里

新特性之Lambda表达式

什么是Lambda表达式

  Lambda是Java 8引入的新特性,可以替代匿名函数,类似于JS的闭包。没有声明的方法,也即没有访问修饰符、返回值声明和名字,它将函数作为参数传入方法中。标志性符号是->,() ->或者() -> {}
  Lambda表达式是隐式地赋值给函数式接口,也就是必须用在函数式接口上,函数式接口是指被@FunctionalInterface注解的接口,例如java.lang.Runnable
  Lambda表达式会隐式的将成员变量或局部变量变成final终态类型。

为什么需要Lambda表达式

  以简洁而紧凑的语言结构来替代匿名内部类。

怎么样使用Lambda表达式

Lambda在循环中使用

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
public static void main(String[] args) {
Map<String,String> map = new LinkedHashMap<>();
map.put("0","h");
map.put("1","u");
map.put("2","a");
map.put("3","n");
map.put("4","g");
map.put("5","d");
map.put("6","a");
map.put("7","y");
map.put("8","u");
map.put("9",".");
map.put("10","c");
map.put("11","n");

//--------------Java7--------------

for (Map.Entry<String,String> m : map.entrySet()) {
	System.out.print(m.getKey()+":"+m.getValue()+",");
}

//--------------Java 8--------------

map.forEach((K,V)->{
	System.out.print(K+":"+V+",");
});
	}

Lambda在线程中使用

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
//--------------Java7--------------

Thread t1 = new Thread(new Runnable() {
    public void run() {
    	System.out.println("Java7:规规矩矩的写法。");
    }
});
t1.start();

new Thread(new Runnable() {
	@Override
	public void run() {
System.out.println("Java7:节省引用的写法。");
	}
}).start();

new Thread() {
	@Override
	public void run() {
System.out.println("Java7:节省内部匿名类的写法。");
	}
}.start();

new Thread(()-> {
	System.out.println("Java 8:Lambda写法,节省匿名内部类与方法。");
}).start();

new Thread(System.out::println).start();

new Thread(Test3::print).start();

Runnable r = () -> {
	System.out.println("Java 8:直接使用函数式接口");
};
r.run();

新特性之函数式接口

什么是函数式接口

  Functional Interface,顾名思义就是函数式接口。它有以下特征:

  • 一个有且仅有一个抽象方法。
  • @FunctionalInterface注解的接口声明是函数式接口,例如java.lang.Runnable,java.util.concurrent.Callable,java.util.function.*
  • 默认方法静态方法对函数式接口不影响。

为什么要函数式接口

  为了让现有的功能与Lmabdas表达式有很好的兼容,不受非抽象函数的影响,和将接口(匿名内部类)隐式转为Lambda表达式。例如java.lang.Runnable接口。

怎么样使用函数式接口

Service.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@FunctionalInterface
public interface Service {
	/***
	 * 抽象方法
	 */
	public abstract void create();
	
	/***
	 * 静态方法:必须有方法体,实现着可以覆盖
	 * @param pid
	 */
	public static void delete(int pid) {};
	
	/***
	 * 默认方法:必须有方法体,实现着可以覆盖
	 */
	public default void update() {};
	
	/**
	 * 默认方法:必须有方法体,实现着可以覆盖
	 * @param pid
	 */
	public default void get(int pid) {};
}

Server.java

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
public class Server implements Service {

	private Service service;

	public Server(Service service) {
		this.service = service;
	};

	public void start() {
		this.service.create();
	};

	public void restart() {
		if (service != null) {
			this.service.create();
		} else {
			this.service.create();
		}
	};

	public void update() {
		this.service.update();
	};

	public void stop(int pid) {
		Service.delete(pid);
	}

	@Override
	public void create() {
		if (service != null) {
			service.create();
		}
	};
}

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
	public static void main(String[] args) {
		// 使用函数接口的匿名内部类
		new Server(new Service() {
			@Override
			public void create() {
				System.out.println("start");
			}
		}).start();
		
		// 实际隐藏式转化为以下Lambda表达式
		new Server(() -> {
			System.out.println("restart");
		}).restart();
	}
}

新特性之方法引用

什么是方法引用

  通过::双冒号特殊符号,可以直接引用Java类的方法,对象,构造器。此表达式的目标类型必须是函数接口。

为什么要方法引用

  与Lambda表达式一起用,可以减少代码冗余。

怎么样使用方法引用

引用方法

  • 引用构造器
  • 引用静态方法
  • 特定对象的方法引用
  • 特定类的任意对象的方法引用

实例

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
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

public class Test5 {

	/***
	 * 
	 * @param supplier 一个函数式接口,可以用作lambda表达式或方法引用的赋值目标。
	 * @return 返回对象
	 */
	public static Test5 a(final Supplier<Test5> supplier) {
		return supplier.get();
	}
	
	/***
	 * 静态方法传入对象
	 * @param t
	 */
	public static void b(final Test5 t) {
		System.out.println(t.toString());
	}

	/***
	 * 无返回值方法传入对象
	 * @param t
	 */
	public void c(final Test5 t) {
		System.out.println(t.toString());
	}

	/***
	 * 无返回值无参数方法
	 */
	public void d() {
		System.out.println(this.toString());
	}
	
	public void e(String s) {
		System.out.println(s);
	}
	
	public static void main(String[] args) {
		//引用构造器
		Test5 t = Test5.a(Test5::new);
		
		List< Test5 > lt = Arrays.asList( t );
		
		//forEach会拿到一个Test5对象
		
		//引用 静态方法
		lt.forEach(Test5::b);
		
		//特定对象的方法引用
		lt.forEach(t::c);
		
		//特定类的任意对象的方法引用
		lt.forEach(Test5::d);
		
	}
	
}

新特性之接口的兼容

什么是接口的兼容

  Java 8引入了默认方法静态方法,这两个方法可以直接在接口中写实现,不影响接口的实现类,也就是解决了接口兼容的问题。

为什么要接口的兼容

  Java 8之前,每一次对接口的修改都会影响其的全部实现。

注意

  • 实现类中不可以重写静态方法
  • 实现类中可以重写默认方法,需要去掉default关键字
  • 实现类中可以通过类.super.默认方法对默认方法进行重载
  • 接口中的默认方法通过对象.方法调用
  • 接口中的静态方法通过类.方法调用

什么是默认方法

  被default 关键字修饰的方法。

什么是静态方法

  被static 关键字修饰的方法。

如何实现接口的兼容

接口实例

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Animal {
	public default void run() {
		System.out.println("run");
	};
	
	public static void eat() {
		System.out.println("eat");
	}
	
	public static void sleep() {
		System.out.println("sleep");
	}
}

实现类实例

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
public class Dog implements Animal {
	
//  报错	
//	@Override
//	public default void run() {
//		Animal.super.run();
//	};
	
	@Override
	public void run() {
		System.out.println("dog run");
	};

	public void run2() {
		Animal.super.run();
	};

//  报错
//	@Override
//	public static void eat() {
//		System.out.println("eat");
//	}

//  报错
//	@Override
//	public void eat() {
//		System.out.println("eat");
//	}
	
	public static void eat() {
		System.out.println("dog eat");
	}
	
	public static void sleep() {
		System.out.println("dog sleep");
	}
	
	public static void main(String[] args) {
		Animal a = new Dog();
		a.run();
		
		Animal.eat();
		Animal.sleep();
	}
}

输出结果

1
2
3
dog run
eat
sleep

新特性之重复注解

什么是重复注解

  同一个地方(类、方法、成员变量、局部变量、泛型类、父类、接口、接口的实现)可以重复声明同一个注解。

为什么要重复注解

  Java5引入了注解机制,但是使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。

如何实现重复注解

@Repeatable注解是关键:表示该注解是一个可以重复的注解。

实例

说明:ElementType.TYPE_USEElementType.TYPE_PARAMETER是用于描述适当的注解上下文的元素类型。

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
/**
 * @author huangdayu.cn
 */
public class Test6 {
	
	@Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
	public @interface Run {
		
	}
	
	@Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
	public @interface Eats {
		/***
		 * 表示返回一个注解数组,方法名必须是value()
		 * @return
		 */
		Eat[] value();
	}
	
	@Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
	@Repeatable( Eats.class )//制作一个可重复的注解
	public @interface Eat {
		
	}
	
	/***
	 * @Eat 可以重复注解
	 * @Run 不可以重复注解
	 *
	 */
	@Eat
	@Eat
	@Run
	public @interface Animal {
		
	}
}

新特性之Optional类

什么是Optional类

  Optional类是Java 8引入的新类,所在类库是java.util.Optional<T>。Optional 是个容器,可以存储类型为T的值,也可以存储为null的对象,也就是说,即使对象为null,也不报java.lang.NullPointerException空对象异常。

为什么要Optional类

  从上面就可以看出,该类就是为了解决臭名昭著的空对象异常而生。

如何使用Optional类

提供的方法

方法 说明
static <T> Optional<T> empty() 返回空的 Optional 实例。
boolean equals(Object obj) 判断其他对象是否等于 Optional。
Optional<T> filter(Predicate<? super <T> predicate) 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
int hashCode() 返回存在值的哈希码,如果值不存在 返回 0。
void ifPresent(Consumer<? super T> consumer) 如果值存在则使用该值调用 consumer , 否则不做任何事情。
boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
<U>Optional<U> map(Function<? super T,? extends U> mapper) 如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
static <T> Optional<T> of(T value) 返回一个指定非null值的Optional,value为null则直接抛出空对象异常。
static <T> Optional<T> ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
T orElse(T other) 如果存在该值,返回值, 否则返回 other,可以视为默认值。
T orElseGet(Supplier<? extends T> other) 如果存在该值,返回值, 否则触发 other(一个Lambda表达式),并返回 other 调用的结果。
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
String toString() 返回一个Optional的非空字符串,用来调试

实例

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
import java.util.Optional;

public class Test9 {
	public static void main(String[] args) {
	
		//如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
		Optional<Object> a = Optional.ofNullable(null);
		Test9.test(a);
		
		//值为null则直接抛出空对象异常
		Optional<String> b = Optional.of("yes");
		Test9.test(b);
	}

	public static <T> void test(Optional<T> t) {

		if (t.isPresent()) {// 该值是否存在,否则为null
			T s = t.get();
			System.out.println("获取到的值:"+s);
		} else {
			System.out.println("值为空!");
		}

		//如果存在该值,返回值, 否则返回Lambda表达式的结果。
		System.out.println(t.orElseGet(() -> {
			return (T) "Lambda表达式返回值!";
		}));

		//如果存在该值,返回值, 否则返回Lambda表达式的结果。
		System.out.println(t.orElse((T) "我是默认值-1!"));
		
		System.out.println(t.map(s -> "值是: " + s + "!\n").orElse("我是默认值-2!\n"));

	}

}

输出结果

1
2
3
4
5
6
7
8
9
值为空!
Lambda表达式返回值!
我是默认值-1!
我是默认值-2!

获取到的值:yes
yes
yes
值是: yes!

新特性之Stream流

什么是Stream流

  java.util.stream.*是Java 8引入的函数式编程类库,以一种声明的方式来处理元素集合里的数据,可以理解为shell脚本中管道|)一样的概念。
  首先将数据源集合数组I/O channel产生器generator)的元素形成队列,然后管道中传输,并且可以在管道的节点上进行聚合操作, 比如筛选排序聚合计算等,这个视为中间操作最终操作将结果返回。Java中将这一系列操作抽象为Stream流

Stream流的分类

  • stream():串行流
  • parallelStream():并行流

PS:串行流与并行流的区别就是单线程与多线程的区别,并行流处理比串行流快很多。具体业务使用具体流,比如并行流对元素的排序(sorted())就返回了错误的结果,对条件判断(filter())则不影响结果。

Stream流的特征

  • Pipelining:中间操作都会返回流对象本身, 如同流式风格(fluent style)的管道。 可以进行延迟执行(laziness)和短路( short-circuiting)等操作。
  • 内部迭代: 通常集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

为什么要Stream流

  为了让开发者能够快速,简洁,高效地对数据源集合数组I/O channel产生器generator)中的元素进行操作。
  凡是提供了stream()方法的集合都可以用流处理。如Collection接口。

如何使用Stream流

接口说明

java.util.stream.* 接口说明

接口 说明
BaseStream<T,S extends BaseStream<T,S» 流的基本接口,它是支持串行和并行聚合操作的元素序列。
Collector<T,A,R> 可变缩减操作,将输入元素累积到可变结果容器中,可选地在处理完所有输入元素后将累积结果转换为最终表示。
DoubleStream 一系列原始双值元素,支持串行和并行聚合操作。
DoubleStream.Builder DoubleStream的可变构建器。
IntStream 支持串行和并行聚合操作的一系列原始int值元素。
IntStream.Builder IntStream的可变构建器。
LongStream 一系列原始长值元素,支持串行和并行聚合操作。
LongStream.Builder LongStream的可变构建器。
Stream<T> 支持串行和并行聚合操作的一系列元素。
Stream.Builder<T> Stream的可变构建器。

Stream的方法说明

java.util.stream.Stream 类的方法说明

返回值类型 方法 说明
boolean allMatch(Predicate<? super T> predicate) 返回此流的所有元素是否与提供的谓词匹配。
boolean anyMatch(Predicate<? super T> predicate) 返回此流的任何元素是否与提供的谓词匹配。
static <T> Stream.Builder<T> builder() 返回Stream的构建器。
<R,A> R collect(Collector<? super T,A,R> collector) 使用收集器对此流的元素执行可变减少操作。
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) Performs a mutable reduction operation on the elements of this stream.
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) 创建一个延迟连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。
long count() 返回此流中元素的数量。
Stream<T> distinct() 返回由此流的不同元素(根据Object.equals(Object))组成的流。
static <T> Stream<T> empty() 返回一个空的顺序Stream。
Stream<T> filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。
Optional<T> findAny() 返回描述流的某个元素的Optional,如果流为空,则返回空Optional。
Optional<T> findFirst() 返回描述此流的第一个元素的Optional,如果流为空,则返回空Optional。
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) 返回一个流,该流包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。
DoubleStream flatMapToDouble(Function<? super T,? extends DoubleStream> mapper) 返回一个DoubleStream,它包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。
IntStream flatMapToInt(Function<? super T,? extends IntStream> mapper) 返回一个IntStream,它包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。
LongStream flatMapToLong(Function<? super T,? extends LongStream> mapper) 返回一个LongStream,它包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。
void forEach(Consumer<? super T> action) 对此流的每个元素执行操作。
void forEachOrdered(Consumer<? super T> action) 如果流具有已定义的遭遇顺序,则按流的遭遇顺序对此流的每个元素执行操作。
static <T> Stream<T> generate(Supplier<T> s) 返回无限顺序无序流,其中每个元素由提供的Supplier生成。
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) 返回通过将函数f迭代应用于初始元素种子而生成的无限顺序有序流,生成由种子,f(seed),f(f(seed))等组成的流。
Stream<T> limit(long maxSize) 返回由此流的元素组成的流,截断长度不超过maxSize。
<R> Stream<R> map(Function<? super T,? extends R> mapper) 返回一个流,该流包含将给定函数应用于此流的元素的结果。
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) 返回DoubleStream,其中包含将给定函数应用于此流的元素的结果。
IntStream mapToInt(ToIntFunction<? super T> mapper) 返回一个IntStream,它包含将给定函数应用于此流的元素的结果。
LongStream mapToLong(ToLongFunction<? super T> mapper)Returns a LongStream consisting of the results of applying the given function to the elements of this stream. 返回一个LongStream,它包含将给定函数应用于此流的元素的结果。
Optional<T> max(Comparator<? super T> comparator) 根据提供的Comparator返回此流的最大元素。
Optional<T> min(Comparator<? super T> comparator) 根据提供的Comparator返回此流的最小元素。
boolean noneMatch(Predicate<? super T> predicate) 返回此流的元素是否与提供的谓词匹配。
static <T> Stream<T> of(T... values) 返回其元素为指定值的顺序有序流。
static <T> Stream<T> of(T t) 返回包含单个元素的顺序Stream。
Stream<T> peek(Consumer<? super T> action) 返回由此流的元素组成的流,此外还在从结果流中消耗元素时对每个元素执行提供的操作。
Optional<T> reduce(BinaryOperator<T> accumulator) 使用关联累加函数执行此流的元素的减少,并返回描述减少值的Optional(如果有)。
T reduce(T identity, BinaryOperator<T> accumulator) 使用提供的标识值和关联累加函数执行此流的元素的减少,并返回减少的值。
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) 使用提供的标识,累积和组合功能,对此流的元素执行减少。
Stream<T> skip(long n) 在丢弃流的前n个元素后,返回由此流的其余元素组成的流。
Stream<T> sorted() 返回由此流的元素组成的流,按照自然顺序排序。
Stream<T> sorted(Comparator<? super T> comparator) 返回由此流的元素组成的流,根据提供的Comparator进行排序。
Object[] toArray() 返回包含此流的元素的数组。
<A> A[] toArray(IntFunction<A[]> generator) 返回一个包含此流元素的数组,使用提供的生成器函数分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。

实例

People.java

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
public class People {
	private String name;
	private int age;
	private String address;
	
	public People(String name,int age,String address) {
		this.name = name;
		this.age = age;
		this.address = address;
	};

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "People [name=" + name + ", age=" + age + ", address=" + address + "]";
	}

}

Test.java

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
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;


public class Test10 {
	public static void main(String[] args) {
		Collection< People > peoples = Arrays.asList(
			    new People( "a",1,"aa" ),
			    new People( "b",2,"bb" ),
			    new People( "c",3,"cc" ), 
			    new People( "d",4,"dd" ),
			    new People( "e",5,"ee" ),
			    new People( "f",6,"ff" ),
			    new People( "g",7,"gg" ),
			    new People( "h",8,"hh" ), 
			    new People( "i",9,"ii" )
		);
		
		System.out.print("\n使用iterator\n");
		// 使用iterator
		for (Iterator iterator = peoples.iterator(); iterator.hasNext();) {
			People people = (People) iterator.next();
			System.out.println(people.toString());
		}

		System.out.print("\n使用lambda\n");
		// 使用lambda
		peoples.forEach(people -> {
			System.out.println(people.toString());
		});

		System.out.print("\n使用方法引用\n");
		// 使用方法引用
		peoples.forEach(System.out::println);

		System.out.print("\n使用Stream流limit()方法\n");

		// Stream提供了 forEach()方法来迭代(Iteration)流中的每个数据
		// Stream提供了limit()方法用于获取指定数量的流。
		peoples.stream().limit(8).forEach(s -> {
			System.out.println(s.toString());
		});

		System.out.print("\n使用Stream流map()方法\n");
		// Stream提供了map()方法用于映射每个元素到对应的结果
		peoples.stream().map(people -> {
			if (people.getAge() == 5) {
				people.setAge(0);
			}
			return people;
		}).forEach(System.out::println);

		System.out.print("\n使用Stream流filter()方法\n");
		// Stream提供了filter()方法用于通过设置的条件过滤出元素。
		// parallelStream提供了count()方法用于统计符合条件的元素的数量
		long a = peoples.parallelStream().filter(people -> {
			if (people.getAge() == 0 || people.getName().equals("a")) {
				return true;
			}
			return false;
		}).count();
		System.out.println("符合条件的总共:" + a);
		
		// Stream提供了sorted()方法用于对元素进行排序
		System.out.print("\n使用Stream流sorted()方法\n");
		peoples.stream().sorted(Comparator.comparing(People::getAge).reversed()).forEach(System.out::println);
	}
}

输出结果

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

使用iterator
People [name=a, age=1, address=aa]
People [name=b, age=2, address=bb]
People [name=c, age=3, address=cc]
People [name=d, age=4, address=dd]
People [name=e, age=5, address=ee]
People [name=f, age=6, address=ff]
People [name=g, age=7, address=gg]
People [name=h, age=8, address=hh]
People [name=i, age=9, address=ii]

使用lambda
People [name=a, age=1, address=aa]
People [name=b, age=2, address=bb]
People [name=c, age=3, address=cc]
People [name=d, age=4, address=dd]
People [name=e, age=5, address=ee]
People [name=f, age=6, address=ff]
People [name=g, age=7, address=gg]
People [name=h, age=8, address=hh]
People [name=i, age=9, address=ii]

使用方法引用
People [name=a, age=1, address=aa]
People [name=b, age=2, address=bb]
People [name=c, age=3, address=cc]
People [name=d, age=4, address=dd]
People [name=e, age=5, address=ee]
People [name=f, age=6, address=ff]
People [name=g, age=7, address=gg]
People [name=h, age=8, address=hh]
People [name=i, age=9, address=ii]

使用Streamlimit()方法
People [name=a, age=1, address=aa]
People [name=b, age=2, address=bb]
People [name=c, age=3, address=cc]
People [name=d, age=4, address=dd]
People [name=e, age=5, address=ee]
People [name=f, age=6, address=ff]
People [name=g, age=7, address=gg]
People [name=h, age=8, address=hh]

使用Streammap()方法
People [name=a, age=1, address=aa]
People [name=b, age=2, address=bb]
People [name=c, age=3, address=cc]
People [name=d, age=4, address=dd]
People [name=e, age=0, address=ee]
People [name=f, age=6, address=ff]
People [name=g, age=7, address=gg]
People [name=h, age=8, address=hh]
People [name=i, age=9, address=ii]

使用Streamfilter()方法
符合条件的总共:2

使用Streamsorted()方法
People [name=i, age=9, address=ii]
People [name=h, age=8, address=hh]
People [name=g, age=7, address=gg]
People [name=f, age=6, address=ff]
People [name=d, age=4, address=dd]
People [name=c, age=3, address=cc]
People [name=b, age=2, address=bb]
People [name=a, age=1, address=aa]
People [name=e, age=0, address=ee]

新特性之Base64编码

什么是Base64编码

  java.util.Base64是Java 8引入的对字符进行编码解码的类库。

为什么要Base64编码

  对字符进行编码和解码,可以运用于字符串,URL,MIME中。

如何使用Base64编码

内嵌类

说明
static class Base64.Decoder 该类实现一个解码器用于,使用 Base64 编码来解码字节数据。
static class Base64.Encoder 该类实现一个编码器,使用 Base64 编码来编码字节数据。

方法

方法 说明
static Base64.Decoder getDecoder() 返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。
static Base64.Encoder getEncoder() 返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
static Base64.Decoder getMimeDecoder() 返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。
static Base64.Encoder getMimeEncoder() 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。
static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。
static Base64.Decoder getUrlDecoder() 返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。
static Base64.Encoder getUrlEncoder() 返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。

实例

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
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Test12 {
	public static void main(String[] args) {
		String s = "huangdayu.cn";
		s = encoder(s);
		System.out.println(s);
		s = decoder(s);
		System.out.println(s);
	}

	/***
	 * 加密
	 * @param s
	 * @return
	 */
	public static String encoder(String s) {
		return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
	}

	/***
	 * 解密
	 * @param s
	 * @return
	 */
	public static String decoder(String s) {
		return new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8);
	}
}

输出结果

1
2
aHVhbmdkYXl1LmNu
huangdayu.cn

新特性之TimeAPI

什么是TimeAPI

  java.time.*是Java 8引入的Date&Time&Zone类库,提供日期,时间,瞬间和持续时间的主要API。

为什么要TimeAPI

  旧的 java.util.Date以及java.util.Calendar被开发者普遍吐槽,所以借鉴了 Joda Time 的优点,开发了新的API。

旧API的问题

  • 非线程安全 : java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 :Java的日期/时间类的定义并不一致,在java.utiljava.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 : 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendarjava.util.TimeZone类,但他们同样存在上述所有的问题。

新API的优点

  • Local(本地) : 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) : 通过制定的时区处理日期时间。
  • 新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

如何使用TimeAPI

类说明

说明
Clock 一个时钟,使用时区提供对当前时刻,日期和时间的访问。
Duration 基于时间的时间量,例如’34.5秒’。
Instant 时间线上的瞬时点。
LocalDate ISO-8601日历系统中没有时区的日期,例如2007-12-03。
LocalDateTime ISO-8601日历系统中没有时区的日期时间,例如2007-12-03T10:15:30。
LocalTime 在ISO-8601日历系统中没有时区的时间,例如10:15:30。
MonthDay ISO-8601日历系统中的一个月 - 例如 - 12-03。
OffsetDateTime 在ISO-8601日历系统中与UTC / Greenwich偏移的日期时间,例如2007-12-03T10:15:30 + 01:00。
OffsetTime 在ISO-8601日历系统中与UTC / Greenwich偏移的时间,例如10:15:30 + 01:00。
Period ISO-8601日历系统中基于日期的时间量,例如“2年,3个月和4天”。
Year ISO-8601日历系统中的一年,例如2007。
YearMonth ISO-8601日历系统中的年度月份,例如2007-12。
ZonedDateTime ISO-8601日历系统中具有时区的日期时间,例如2007-12-03T10:15:30 + 01:00欧洲/巴黎。
ZoneId 时区ID,例如Europe / Paris。
ZoneOffset 与格林威治/ UTC的时区偏移,例如+02:00。

枚举类说明

枚举类 说明
DayOfWeek 星期几,例如’星期二’。
Month 一个月,例如’七月’。

异常

异常 Description
DateTimeException 用于表示计算日期时间问题的异常。

实例

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
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Test11 {

	public static void main(String[] args) {
		Clock clock = Clock.systemUTC();
		System.out.println(clock.instant());
		System.out.println(clock.millis());
		System.out.println(clock.getZone());

		// 获取本地日期
		final LocalDate date = LocalDate.now();
		final LocalDate dateFromClock = LocalDate.now(clock);

		System.out.println(date);
		System.out.println(dateFromClock);

		// 获取本地时间
		final LocalTime time = LocalTime.now();
		final LocalTime timeFromClock = LocalTime.now(clock);

		System.out.println(time);
		System.out.println(timeFromClock);

		// 获取本地日期和本地时间,是LocalTime和LocalDate的集合
		final LocalDateTime datetime = LocalDateTime.now();
		final LocalDateTime datetimeFromClock = LocalDateTime.now(clock);

		System.out.println(datetime);
		System.out.println(datetimeFromClock);

		// 获取分区日期/时间
		final ZonedDateTime zonedDatetime = ZonedDateTime.now();
		final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now(clock);
		final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));

		System.out.println(zonedDatetime);
		System.out.println(zonedDatetimeFromClock);
		System.out.println(zonedDatetimeFromZone);

		// 获取两个日期之间的间隔时间
		final LocalDateTime from = LocalDateTime.of(2014, Month.APRIL, 16, 0, 0, 0);
		final LocalDateTime to = LocalDateTime.of(2015, Month.APRIL, 16, 23, 59, 59);

		final Duration duration = Duration.between(from, to);
		System.out.println("间隔多少天: " + duration.toDays());
		System.out.println("间隔多少小时: " + duration.toHours());
	}

}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2019-04-15T05:54:13.750Z
1555307653835
Z
2019-04-15
2019-04-15
13:54:13.850
05:54:13.850
2019-04-15T13:54:13.850
2019-04-15T05:54:13.850
2019-04-15T13:54:13.850+08:00[Asia/Shanghai]
2019-04-15T05:54:13.850Z
2019-04-14T22:54:13.850-07:00[America/Los_Angeles]
间隔多少天: 365
间隔多少小时: 8783

新特性之JavaScript引擎

什么是JavaScript引擎

  早在Java 6时就引入了JavaScript引擎Rhino,它支持ECMAScript 5.1规范,它使用JSR 292言特性。Java 7时引入了invokedynamic,将JavaScript编译成Java字节码。Java 8引入的新的JavaScript引擎NashornRhino性能提高多倍,Nashorn就是javax.script.ScriptEngine的另一种实现。

什么是jjs

  jjs是个基于Nashorn引擎的命令行工具。它可以接受JavaScript源代码为参数,并且执行这些源代码。

为什么要JavaScript引擎

  为了使JavaJavaScript两门语言在JVM环境下能相互调用。

如何使用JavaScript引擎

jjs命令行实例

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
C:\Users\Administrator\Desktop\back\new 
λ touch test.js                         
                                        
C:\Users\Administrator\Desktop\back\new 
λ vim test.js                           
                                        
C:\Users\Administrator\Desktop\back\new 
λ cat test.js                           
print("This is JavaScript test code."); 

C:\Users\Administrator\Desktop\back\new 
λ jjs test.js                           
This is JavaScript test code.           

# 交互式编程

C:\Users\Administrator\Desktop\back\new
λ jjs
jjs> print("this is JavaScript test code")
this is JavaScript test code
jjs>

# 传递参数

C:\Users\Administrator\Desktop\back\new
λ jjs -- a b c
jjs> print("传递的参数:"+arguments.join(","))
传递的参数:a,b,c
jjs>

Java调用JavaScript

实例

Test.java

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
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Test13 {
	public static void main(String[] args) {
		java_JavaScript();
	}
	
	private static void java_JavaScript() {
		ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
		ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");

		String name = "huangdayu.cn";
		Integer result = null;

		try {
			nashorn.eval("print('" + name + "')");
			result = (Integer) nashorn.eval("10 + 2");

		} catch (ScriptException e) {
			System.out.println("执行脚本错误: " + e.getMessage());
		}

		System.out.println(result.toString());
	}
	
}

输出结果

1
2
huangdayu.cn
12

JavaScript调用Java

实例

test2.js

1
2
3
4
5
6
7
8
9
10
var BigDecimal = Java.type('java.math.BigDecimal');

function calculate(amount, percentage) {
	var result = new BigDecimal(amount).multiply(
    	new BigDecimal(percentage)).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
    	return result.toPlainString();
}

var result = calculate(568000000000000000023,13.9);
print(result);

输出结果

1
2
3
4
5
6
F:\Android\Java\Eclipse Project SSM\NewCharacteristic\js
λ touch test2.js

F:\Android\Java\Eclipse Project SSM\NewCharacteristic\js
λ jjs test2.js
78952000000000002017.94

参考文献

Java 8 官方文档
Java 8 新特性
Java 8 新特性终极指南
Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念
阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义
阿里巴巴Java开发手册(详尽版)
知乎问答
知乎问答
面向对象的三个基本特征 和 五种设计原则
Java面向对象的三个特征与含义

今日诗词

作者信息