并行流与串行流
什么是并行流
我们先了解一下什么是Fork/Join框架
Fork/Join框架和传统的线程池的区别
应用Fork/Join计算100000000的和:
public class ForkJoinCalculate extends RecursiveTask<Long> {
/**
*
*/
private static final long serialVersionUID = 13475679780L;
private long start;
private long end;
private static final long THRESHOLD = 10000L; //临界值
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THRESHOLD){
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork(); //拆分,并将该子任务压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);
right.fork();
return left.join() + right.join();
}
}
}
测试代码如下:
@Test
public void test() {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinCalculate(0, 100000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:"+Duration.between(start, end).toMillis());
}
执行结果如下图:
数值越大,它的效率体现的就越明显;
在Java8里面可以利用并行流就可以这样来写:它的底层就是Fork/Join
@Test
public void test3() {
Instant start = Instant.now();
LongStream.rangeClosed(0, 100000000L)
.parallel()
.reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println("耗费时间为:"+Duration.between(start, end).toMillis());
}
Optional容器类
什么是Optional类
Optional类的常用方法
一、Optional 容器类:用于尽量避免空指针异常
- Optional.of(T t) : 创建一个 Optional 实例
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
- isPresent() : 判断是否包含值
- orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
- orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
- map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
- flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
- Optional.of(T t) : 创建一个 Optional 实例
@Test
public void test1() {
Optional<Employee> op = Optional.of(new Employee());
Employee employee = op.get();
System.out.println(employee);
}
运行结果如下:没有传任何的参数,这个时候,结果就是默认的值
Employee [id=0, name=null, age=0, salary=0.0, status=null]
需要注意的地方就是:
如果这里面传入了一个Null值,那么这里就是会发生空指针异常,原来的我们定位空指针异常要一行一行的去定位,非常的麻烦。它的思想就是:如果传进来了一个Null,在这一行就会发生空指针的异常,方便我们快速定位到空指针异常发生的地方:
Optional<Employee> op = Optional.of(null);
- Optional.empty() : 创建一个空的 Optional 实例
@Test
public void test2() {
Optional<Employee> op = Optional.empty();
System.out.println(op.get());
}
在这一行发生了空指针异常:
System.out.println(op.get());
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
- 若 t 不为 null,创建 Optional 实例
@Test
public void test3() {
Optional<Employee> op = Optional.ofNullable(new Employee());
System.out.println(op.get());
}
- 若t为Null,那么则创建空的实例
@Test
public void test3() {
Optional<Employee> op = Optional.ofNullable(null);
System.out.println(op.get());
}
- isPresent() : 判断是否包含值
我们可以进行判断:有值的时候,就正常的调用;如果没有值的话,那么就什么都不做;
@Test
public void test4() {
Optional<Employee> op = Optional.ofNullable(null);
if (op.isPresent()) {
System.out.println(op.get());
}
}
- orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
如果有值的话,那么就返回值,如果没有值的话,那么我们就返回这个备用的值:T
如果有值的话,那么就是返回了这个值:
@Test
public void test5() {
Optional<Employee> op = Optional.ofNullable(new Employee());
if (op.isPresent()) {
System.out.println(op.get());
}
Employee employee = op.orElse(new Employee(1, "张三", 18, 888.88, Employee.Status.FREE));
System.out.println(employee);
}
结果如下:
Employee [id=0, name=null, age=0, salary=0.0, status=null]
Employee [id=0, name=null, age=0, salary=0.0, status=null]
如果没有值的话,那么就是返回我们备用的值
@Test
public void test5() {
Optional<Employee> op = Optional.ofNullable(null);
if (op.isPresent()) {
System.out.println(op.get());
}
Employee employee = op.orElse(new Employee(1, "张三", 18, 888.88, Employee.Status.FREE));
System.out.println(employee);
}
这个时候,就是只输出一个值:也就是当为空时候,输出我们备用的值
Employee [id=1, name=张三, age=18, salary=888.88, status=FREE]
- orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
orElseGet这个方法里面的参数是一个供给型的接口,可以实现具体的功能:
@Test
public void test6() {
Optional<Employee> op = Optional.ofNullable(null);
Employee employee = op.orElseGet(() -> new Employee());
System.out.println(employee);
}
- map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
@Test
public void test7() {
Optional<Employee> op = Optional.ofNullable(new Employee(1, "张三", 18, 888.88, Employee.Status.FREE));
Optional<String> str = op.map((e) -> e.getName());
System.out.println(str.get());
}
- flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
进一步避免空指针异常
@Test
public void test8() {
Optional<Employee> op = Optional.ofNullable(new Employee(1, "张三", 18, 888.88, Employee.Status.FREE));
Optional<String> opStr = op.flatMap((e) -> Optional.of(e.getName()));
System.out.println(opStr.get());
}
一般我们在创建可能为空的属性的时候,我们就是可以这样来写了:
我们就是可以用Optional这个类来进行封装一下:
private Optional<Godness> godness = Optional.empty();
这个时候,就是可以避免空指针异常了:
这个时候,就是可以避免空指针异常了;
如果,我们传了,就是我们传的值,如果没有传的话,那么也是有默认的值,就有效的避免了空指针的异常了:
接口中的默认方法与静态方法
接口中的默认方法
接口中的默认方法有一个类优先的原则:
- 有一个接口,里面有一个默认的方法:
public interface MyFun {
default String getName() {
return "Hello World";
}
}
- 有一个类,里面也有一个与上面接口同样名字的方法:
public class MyClass {
public String getName() {
return "I love Java";
}
}
- 此时,有一个类同时继承了上面的类和实现了上面的接口:
public class SubClass extends MyClass implements MyFun{
}
- 现在,我们创建SubClass这个类的实例,并且调用了实例的getName方法:
public static void main(String[]args){
SubClass subClass = new SubClass();
System.out.println(subClass.getName());
}
- 此时,运行的结果如下:
需要注意的地方:
如果一个类里面实现了多个接口,而这多个接口里面有重名的方法,那么这个时候,在实现里面必须指定重写哪一个接口的对应的方法;
接口中的静态方法:
public interface MyInterface {
default String getName() {
return "hello java";
}
public static void show() {
System.out.println("接口中的静态方法");
}
}
用法还是和类里面的静态方法一样用的:
public static void main(String[]args){
MyInterface.show();
}
传统时间格式化的线程安全问题
全新的时间都在这几个包下面:
这些都是常用的:
public static void main(String[]args) throws Exception{
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return sdf.parse("20181111");
}
};
/** 创建长度为10的线程池 */
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add((pool.submit(task)));
}
for (Future<Date> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
如下图:
看执行结果,是存在线程安全的问题的:
传统的时间API都存在线程安全的问题;
原来,我们面对线程安全问题,我们可以对其加锁;
- 我们可以使用ThreadLocal
public class DateFormatThreadLocal {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public static Date convert(String source) throws ParseException {
return df.get().parse(source);
}
}
然后,我们就是可以这样来用:
public static void main(String[]args) throws Exception{
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return DateFormatThreadLocal.convert("20181111");
}
};
/** 创建长度为10的线程池 */
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add((pool.submit(task)));
}
for (Future<Date> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
这个时候的执行结果就是:
Java8全新的API:它是线程安全的
public class TestSimpleDateFormat {
public static void main(String[]args) throws Exception{
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
Callable<LocalDate> task = new Callable<LocalDate>() {
@Override
public LocalDate call() throws Exception {
return LocalDate.parse("20181111",dtf);
}
};
/** 创建长度为10的线程池 */
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add((pool.submit(task)));
}
for (Future<LocalDate> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
}
执行结果如下:
新时间与日期API-本地时间与时间戳
public class TestLocalDateTime {
/**
* 1.LocalDate 专门用来表示日期的
* 2.LocalTime 专门用来表示时间的
* 3.LocalDateTime 用来表示时间和日期
*/
@Test
public void test1() {
/** 他们的使用方式大同小异,这里,我们就使用LocalDateTime来进行举例 */
/** 1.使用他们的静态方法now()来获取实例 */
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);//2018-11-11T14:09:39.209
/** 2.还可以使用另外一种方式来获取实例的方法 */
LocalDateTime ldt2 = LocalDateTime.of(2018, 11, 11, 14, 06, 30);
System.out.println(ldt2);//2018-11-11T14:06:30
/** 对日期和时间进行一些运算 */
LocalDateTime ldt3 = ldt.plusYears(2);
System.out.println(ldt3);//2020-11-11T14:09:39.209
/** 做减法的:减上两个月*/
LocalDateTime ldt4 = ldt.minusMonths(2);
System.out.println(ldt4);//2018-09-11T14:11:07.434
/** 利用get方法来进行获取对应的年月日时分秒 */
System.out.println(ldt.getYear());//2018
System.out.println(ldt.getMonth());//NOVEMBER
System.out.println(ldt.getMonthValue());//获取数字的月份:11
System.out.println(ldt.getDayOfMonth());//11
System.out.println(ldt.getDayOfWeek());//SUNDAY
System.out.println(ldt.getDayOfYear());//315
System.out.println(ldt.getHour());//14
System.out.println(ldt.getMinute());//15
System.out.println(ldt.getSecond());//28
}
/** 2.Instant:时间戳(以Unix元年,1970年1月1日 00:00:00 到某个时间之间的毫秒值)*/
@Test
public void test2() {
Instant instant = Instant.now();//默认是UTC时区的时间
System.out.println(instant);//2018-11-11T08:58:19.022Z
//我们也可以指定一个偏移量的运算
OffsetDateTime odt = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);//2018-11-11T16:58:19.022+08:00
//转成毫秒时间
System.out.println(instant.toEpochMilli());//1541926797229
//在1970年1月1日 00:00:00的这个时间增加偏移量
Instant instant1 = Instant.ofEpochSecond(60);
System.out.println(instant1);//1970-01-01T00:01:00Z
}
//Duration:计算两个时间的间隔的
@Test
public void test3() {
Instant ins1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
System.out.println(duration.toMillis());//1000 得到秒的话,就是用这个方法duration.getSeconds()
System.out.println("----------------------");
//这个是我们的时间
LocalTime lt1 = LocalTime.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
LocalTime lt2 = LocalTime.now();
System.out.println(Duration.between(lt1, lt2).toMillis());//1000
}
//Period:计算两个日期的间隔的
@Test
public void test4() {
LocalDate ld1 = LocalDate.of(2015, 9, 6);
LocalDate ld2 = LocalDate.now();
Period period = Period.between(ld1, ld2);
System.out.println(period.getYears());//3年
System.out.println(period.getMonths());//2个月
System.out.println(period.getDays());//5天
}
}
时间校正器
//TemporalAdjusters:时间校正器
@Test
public void test5() {
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);//2018-11-11T17:38:37.993
//将月中的天指定为10
LocalDateTime ldt2 = ldt.withDayOfMonth(10);
System.out.println(ldt2);//2018-11-10T17:38:37.993
//时间校正器
LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(ldt3);//2018-11-18T17:42:38.099
//自定义:下一个工作日是什么时候
LocalDateTime ldt5 = ldt.with((l)->{
LocalDateTime ldt4 = (LocalDateTime)l;
DayOfWeek dow = ldt4.getDayOfWeek();
if (dow.equals(DayOfWeek.FRIDAY)) {
return ldt4.plusDays(3);
} else if (dow.equals(DayOfWeek.SATURDAY)) {
return ldt4.plusDays(2);
} else {
return ldt4.plusDays(1);
}
});
System.out.println(ldt5);
}
时间格式化与时区的处理
- 时间格式化
//DateTimeFormatter 格式化时间/日期
@Test
public void test6() {
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
DateTimeFormatter dtf1 = DateTimeFormatter.ISO_DATE;
LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);
System.out.println(strDate);//2018-11-11T17:53:13.901
String strDate2 = ldt.format(dtf1);
System.out.println(strDate2);//2018-11-11
//我们也可以指定自己的格式
DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String strDate3 = dtf3.format(ldt);
System.out.println(strDate3);//2018年11月11日 17:56:53
//也可以这样来调
String strDate4 = ldt.format(dtf3);
System.out.println(strDate4);//2018年11月11日 17:57:37
/** 如果把字符串解析回日期 */
LocalDateTime newDate = LocalDateTime.parse(strDate4,dtf3);
System.out.println(newDate);//2018-11-11T18:02:30
}
- 时区的处理
@Test
public void test8() {
//按给定的时区来获取时间
LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Athens"));
System.out.println(ldt);//2018-11-11T12:07:53.755
LocalDateTime ldt2 = LocalDateTime.now();
ZonedDateTime zdt = ldt2.atZone(ZoneId.of("Europe/Athens"));//这个就是带时区的日期和时间
System.out.println(zdt);//2018-11-11T18:10:13.425+02:00[Europe/Athens]
}
@Test
public void test7() {
//获取所有的时区
Set<String> set = ZoneId.getAvailableZoneIds();
set.forEach(System.out::println);
}
重复注解与类型注解
定义一个注解容器类:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
定义一个注解:
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "I love Java";
}
这个时候,我们就是可以这样来用了:
public class TestAnnotation {
@Test
public void test1() throws NoSuchMethodException {
Class<TestAnnotation> clazz = TestAnnotation.class;
Method m1 = clazz.getMethod("show");
MyAnnotation[] myAnnotations = m1.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : myAnnotations) {
System.out.println(myAnnotation.value());//hello world
}
}
@MyAnnotation(value = "hello")
@MyAnnotation(value = "world")
public void show() {
}
}