并发问题

并发问题

在编程的时候我们经常会碰到并发的问题,如果处理不好很有可能造成业务数据的错误。我们思考,到底什么是并发问题?
简单的来说我们可以把并发问题归纳为:未写入而先读取 带来的问题。

我们用最简单的取钱的模型来描述这个问题:

compare

在①②③④ 这个几个步骤中,①②和③④分别是两个独立的过程,如果执行的顺序是 ①③②④,这样就会带来最终余额为负的现象,这个就是一个简单的并发问题。

我们可以用代码简单的模拟这个问题:

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

public class AppTest {
private int count = 0;
public static void main(String[] args) {
long ts = System.currentTimeMillis();
AppTest app = new AppTest();
List<Thread> tList = new ArrayList<>(500);

for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
for (int m = 0; m < 1000; m++) {
app.count();
}
});
tList.add(thread);
}
//启动线程
for (Thread t : tList) {
t.start();
}
//等待所有线程执行完
for (Thread t : tList) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println(app.count);
long tend = System.currentTimeMillis();
System.out.println(tend-ts);
}

private void count() {
count++;
}
}

上述代码执行结果: 9997990(结果具有不确定性,此次结果就偶然一次结果),耗时:3378

造成这个结果的原因就是,在多线程执行的过程中,count的值还没有来得及写入内存,另一个线程就已经把count的读取,就导致count少一次count++运算。

解决并发

既然我们已经知道并发问题,如何解决? 对于并发的解决思路是:保证读取的时候,写入已经完成。具体方法有两种,分别是锁和CAS操作。

使用锁 synchronized

修改count()方法:

1
2
3
4
5
6

private synchronized void count() {
count++;
}


运行结果:10000000 耗时:4176

锁的方法导致性能下降很多。

使用CAS操作

把int类型的count换成AtomicInteger类型

1
2
3
4
5
6
7
private AtomicInteger count = new AtomicInteger();

private void count() {
count.addAndGet(1);
}


修改上述两出代码得到运行结果:10000000 耗时:3305

Java对象头

对象是什么

Java是一个面向对象的语言,在Java中可以使用new关键字来产生一个对象,但这个对象到底是什么,应该具有哪些属性?

首先跟我们自己先想下,Java对象已经有以下几个属性:

  1. 属于哪个类
  2. 有哪些字段和类型
  3. 对象的值
    ….

在HotSpot虚拟机中,真实的Java对象是分成三个部分:

  1. 对象头
  2. 对象的值
  3. 对象的填充字节 (在JVM中,要求对象占用内存的大小应该是8bit的倍数,这个信息是用来补齐8bit的,无其他作用

对象头

对象头是Java中对象都具有的属性,是jvm在编译和运行阶段读取的信息。对象头包含三个部分:

  1. mark word
  2. 指针向类的指针
  3. 数组的长度(只有数组的对象用到)

这3个中最复杂的是MarkWordMarkWord用于存储对象自身的运行时数据,
如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。在32bit的环境中,Java头存储的信息如下

Java对象头

对象头的应用



偏向锁

在HotSpot的虚拟机中,当一个线程访问同步块并尝试获取锁时,会在对象头和栈帧中的记录里存储锁的偏向的线程ID,以后该线程进入和退出同步块的代码时候,不需要再进行加锁 ,只需要检测下MarkWord中的是否是当前线程的偏向锁。如果成功,则说明已经获得了锁,如果检测不成功,就再进行加锁。

轻量级锁

线程在执行同步块之前,JVM会现在当前的线程的栈帧中创建用于存储锁记录的空间,并肩对象头的MarkWord复制到锁的记录中,然后线程尝试将对象头MarkWord替换为指向锁记录的指针,如果不成功,说明锁存在竞争,当前线程开始进行盲等来获取锁。

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

@Test
public void stringToFile() throws Exception {
String name = "Test";
byte[] bytes = name.getBytes();
FileOutputStream fileOutputStream = new FileOutputStream("D:/test.txt");
fileOutputStream.write(bytes);
fileOutputStream.close();
}

```
当然同样的,从文件到字符串也是离不了字节这个单位:

``` Java

@Test
public void fileToString() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:/test.txt");
byte[] inByte = new byte[100];
fileInputStream.read(inByte);
String str=new String(inByte);
System.out.println(str);
}

以上我们可以用下面的模型图来表示:

Java Stream模型

流是什么?

从上面的代码中,我们可以看到,有一个 FileInputStreamFileOutputStream这两个流的对象,对于这个对象我们怎么理解?

流是一个字节转移的介质,如同用吸管喝水,水是字节的集合的文件,吸管就是流。

输入流:顾名思义,就是从外界到程序中的介质。

输出流:对外界进行输出的介质

Java 中常用的输入流和输出流:

Java Stream模型

字符流

什么是字符流?用什么用?

还是用喝水来说,我们怎么获得吸管里面水的属性?我们比如获得吸管中某一段水的重量怎么获得?

字符流是流的一种工具,是能够获得流中的单个字节单位的工具。

对应的是Reader和Writer.

我们可以把流转成Reader和Writer工具,来进行处理我们的文件和字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void streamWriteTest() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:/test.txt");
InputStreamReader reader = new InputStreamReader(fileInputStream);
char[] ch =new char[100];
reader.read(ch);
System.out.println(ch);

}

```

``` Java

@Test
public void streamTest() throws Exception {
String str = "test";
FileOutputStream fileOutputStream = new FileOutputStream("D:/Test.txt");
OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream);
writer.write(str.toCharArray());
writer.close();

}

整个的过程入如下:

Java Stream模型

Java反射超越泛型

BeanUtils.copyProperties引发的血案

在一次使用BeanUtils.copyProperties的方法是,莫名其妙的报错,产生的代码分解如下:

Java反射超越泛型

1
2
3
4
5
6
7
8
9
10
11
12

EntityA entityA = new EntityA();
List<UniteA> uniteAList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
UniteA uniteA = new UniteA();
uniteA.setAge(i);
uniteA.setName("name" + i);
uniteAList.add(uniteA);
}
entityA.setKey(10);
entityA.setUniteList(uniteAList);

把EntityA的值赋值给EntityB:

1
2
3
4
5

EntityB entityB = new EntityB();
BeanUtils.copyProperties(entityA, entityB);
System.out.println(entityB.getUniteList().get(0).getClass());

在运行这段代码的时候,发生了异常的信息:

Java反射超越泛型

对于这个异常,我观察了很久,在调试的时候发现,EntityA中的UniteA中的字段竟然能够赋值给 EntityB中UnitB中的字段!! 类型不一样,竟能够存储!!

Java反射超越泛型


泛型仅仅适用于编译期


对于这个问题,归根于Java的泛型的特点,泛型仅仅适用于编译期,下面我们写几个代码来验证这个问题:

1
2
3
List<String> strList=new ArrayList<>();
strList.add("1234");//正常的编译
strList.add(1234);//编译不能通过

上面的例子我们可以看到List的类型的集合无法直接添加一个整型数据,但这个仅仅在编译的才校验,我们可以是有反射绕过这个验证。

1
2
3
4
5
6
7
8
9
10
11

List<String> strList=new ArrayList<>();
strList.add("1234");//正常的编译
try {
Method method=strList.getClass().getDeclaredMethod("add",Object.class);
method.invoke(strList,1234);
System.out.println(strList);
} catch (Exception e) {
e.printStackTrace();
}

调试信息如下:

Java反射超越泛型

同样,我们也可以使用如下代码验证上面的结论:


List<String> strList=new ArrayList<>();
List<Integer> intList=new ArrayList<>();
System.out.println(strList.getClass().equals(intList.getClass()));

打印结果为:true

本文代码文件:下载

Java中lambda表达式详解

为什么使用lambda

在Java中我们很容易将一个变量赋值,比如int a =0;int b=a;

但是我们如何将一段代码和一个函数赋值给一个变量?这个变量应该是什么的类型?

lambda

在Javascript中,可以用一个对象来存储。

1
2
3
4
5
6
7
var t=function()
{
int a=1;
a=a+1;
alert(a);
}

在Java中,直到Java8的lambda的特性问世,才有办法解决这个问题



什么是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

interface eat
{
void eatFood();
}

public static void main(String[] args)
{

eat e = () -> System.out.printf("hello\n");
e.eatFood();

eat e1 = new eat()
{
@Override
public void eatFood()
{
System.out.printf("anoymous class\n");
}
};
e1.eatFood();
}

上面的代码中,e是一个lambda的对象,根据Java的继承的特性,我们可以说e对象的类型是继承自eat接口。而e1是一个正常的匿名类的对象.

通过对比, 可以说 lambda的表达式其实是接口的实现的“另一种方式”。这种方式更加简洁,更容易阅读。除了代码层面的简洁外,在编译的结果时候lambda也不会产生一个多余的匿名类。

对于eat这个特殊的接口,称之为:函数式接口



lamda的优点


  • 代码缩减

compare

  • Option的使用简化代码

    假如我们有个方法,能够产生一个Option对象std

    1
    2
    3

    Option<Person> std=getStudent();

  1. 是否为空的判断

    lambda

  2. 返回不为空的对象

    compare

  3. 多重if else的简化

    lambda



函数式接口

什么是函数式接口?这个是我们理解Lambda表达式的重点,也是产生lambda表达式的**”母体”**,这里我们引用一个比较容易理解的说法:

函数式接口是 一个只有一个抽象方法(不包含object中的方法)的接口。

这个需要说明一点,就是在Java中任何一个对象都来自Object 所有接口中自然会继承自Object中的方法,但在判断是否是函数式接口的时候要排除Object中的方法,下面举几个例子如下:

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
//这个是函数式接口
interface eat
{
void eatFood();
}


//这个不是是函数式接口
interface eat
{
default void eatFood()
{
System.out.println("hello");
};
}



//这个是一个函数式接口
interface eat
{
void eatFood();
String toString();
}

对于是否是函数式接口,Java8中也提供了一个专用的注解:@FunctionalInterface。通过这个注解,可以确定是否是函数式接口:

1
2
3
4
5
6
7
8
9
10
11
12

//此处会报编译错误
@FunctionalInterface
interface eat
{
default void eatFood()
{
System.out.println("hello");
};
}


下面我们写一段lambda简单的代码,找出指定的身份证号码,并打印出来。

lambda


最终的调用:

lambda


对于上面的代码实现,在我们调用excutor方法前,并不知道findName的实现方法,直到在最后把一个方法作为参数传入到excutor方法中。

反思:函数式接口NameCheckInterface,是不是可以用来表示所有返回值为bool类型的,有两个形参(类型是passager 和String类型)的lambda表达式?

如果我们再配合泛型的话,是不是我们只需要定义一个通用的函数式接口?下面我们改写下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

@FunctionalInterface
public interface NameCheckInterface<T,T1,T2>
{
T2 findName(T passager,T1 name);
}

@FunctionalInterface
public interface PrintInterface<T>
{
void printName(T name);
}

private void excutor(List<passager> passagerList, NameCheckInterface<Boolean,passager,String> checker, PrintInterface<String> printer)
{
for (passager p : passagerList) {
if (checker.findName(p,"李四")){
printer.printName(p.getPassagerNo());
}
}
}

对应的调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void simpTest()
{
List<passager> passagerList = new ArrayList<>();
passagerList.add(new passager("李四", "123456789"));
passagerList.add(new passager("张三", "123456789"));
passagerList.add(new passager("王二", "123456789"));

excutor(passagerList,(p,str)->p.getName().equals(str),str-> System.out.println(str));
}



对于这段代码,可以得出lambda中的函数式接口是可以公用的,而jdk中也已经定义了很多通用的函数式接口。



常用的函数式接口

在jdk中通用的函数式接口如下(都在Java.util.function包中):

1
2
3
4
5
6
7
8

Runnable r = () -> System.out.printf("say hello");//没有输入参数,也没有输出
Supplier<String> sp = () -> "hello";//只有输出消息,没有输入参数
Consumer<String> cp = r -> System.out.printf(r);//有一个输入参数,没有输出
Function<Integer, String> func = r -> String.valueOf(r);//有一个输入参数 有一个输出参数
BiFunction<Integer, Integer, String> biFunc = (a, b) -> String.valueOf(a + b);//有两个输入参数 有一个输出参数
BiConsumer<Integer, Integer> biCp = (a, b) -> System.out.printf(String.valueOf(a + b));//有两个输入参数 没有输出参数

PS:上面是基本的方法,其他的都是基于这几个扩展而来

如果上面的代码使用jdk中的函数式接口的话,就不用额外的定义NameCheckInterface和PrintInterface 接口了。根据上面的参数和返回值的形式,可以使用BiFunction和Consumer直接改写excutor方法:

1
2
3
4
5
6
7
8
9
10

private void excutor(List<passager> passagerList, BiFunction<passager,String,Boolean> checker, Consumer<String> printer) {
for (passager p : passagerList) {
if (checker.apply(p,"李四")){
printer.accept(p.getPassagerNo());
}
}

}



函数的引用

从上面的demo中,使用通用的函数表达式能够减少自定义函数式接口,为了进一步简化代码,lambda表达式可以改写成函数的引用的形式

函数的引用是lambda表达式的更简洁的一种写法,也是更能体现出函数式编程的一种形式,让我们更能理解lambda终归也是一个**”函数的对象”**。 下面我们改写一个例子:

1
2
3
4
5
6

Consumer<String> c1 = r -> System.out.printf(r);
c1.accept("1");
Consumer<String> c2 =System.out::printf;
c1.accept("2");

在上面的demo中lambda表达式被我们改写成System.out::printf这个形式,等于我们把一个函数直接赋值给了一个c2对象,这里我们可以俗称(非官方)c2为Java函数的一个对象,这个也结局填补了Java中一个空白。



函数引用的规则

对于Java中lambda改成函数的引用要遵循一定的规则,具体可以分为下面的四种形式:

  1. 静态方法的引用

    如果函数式接口的实现恰好可以通过调用一个静态方法来实现,那么就可以使用静态方法引用

    StaticMethod

    1
    2
    3
    4
    5
    6
    	
    Consumer<String> c1 = r -> Integer.parseInt(r);
    c1.accept("1");
    Consumer<String> c2 =Integer::parseInt;
    c1.accept("2");

  2. 实例方法引用

    如果函数式接口的实现恰好可以通过调用一个实例方法来实现,那么就可以使用实例方法引用

    StaticMethod

    1
    2
    3
    4
    5
    6

    Consumer<String> ins1 = r -> System.out.print(r);
    c1.accept("1");
    Consumer<String> ins2 =System.out::print;
    c1.accept("2");

  3. 对象方法引用

    抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果函数式接口的实现能由上面说的实例方法调用来实现的话,那么就可以使用对象方法的引用。

    instMethod

    1
    2
    3
    4
    5

    Function<BigDecimal,Double> fuc1=t->t.doubleValue();
    fuc1.apply(new BigDecimal("1.025"));
    Function<BigDecimal,Double> fuc2=BigDecimal::doubleValue;
    fuc2.apply(new BigDecimal("1.025"));
    1
    2
    3
    4
    BiFunction<BigDecimal, BigDecimal, BigDecimal> func3 = (x, y) -> x.add(y);
    func3.apply(new BigDecimal("1.025"), new BigDecimal("1.254"));
    BiFunction<BigDecimal, BigDecimal, BigDecimal> func4 = BigDecimal::add;
    func4.apply(new BigDecimal("1.025"), new BigDecimal("1.254"));
  4. 构造方法引用

    如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用。

    instMethod ]

    1
    2
    3
    4
    Consumer<String> n1 = r ->new BigDecimal(r);
    c1.accept("1");
    Consumer<String> n2 =BigDecimal::new;
    c1.accept("2");

stream API的引用

Stream是处理数组和集合的API,Stream具有以下特点:

  • 不是数据结构,没有内部存储
  • 不支持索引访问
  • 延迟计算
  • 支持过滤,查找,转换,汇总等操作

对于StreamAPI的学习,首先需要弄清楚lambda的两个操作类型:中间操作和终止操作。 下面通过一个demo来认识下这个过程。

1
2
3
4
Stream st=Arrays.asList(1,2,3,4,5).stream().filter(x->{
System.out.print(x);
return x>3;
});

当我们执行这段代码的时候,发现并没有任何输出,这是因为lambda表达式需要一个终止操作来完成最后的动作。 我们修改代码:

1
2
3
4
5
6
Stream st=Arrays.asList(1,2,3,4,5).stream().filter(x->{
System.out.print(x);
return x>3;
});

st.forEach(t-> System.out.print(t));

对应的输出结果是:

1
2
3

1234455

为什么会有这个输出呢?因为在filter函数的时候并没有真正的执行,在forEach的时候才开始执行整个lambda表达式,所以当执行到4的时候,filter输出之后,forEach也执行了,最终结果是1234455


对于Java中的lambda表达式的操作,可以归类和整理如下:

中间操作:

  • 过滤 filter
  • 去重 distinct
  • 排序 sorted
  • 截取 limit、skip
  • 转换 map/flatMap
  • 其他 peek

终止操作

  • 循环 forEach
  • 计算 min、max、count、 average
  • 匹配 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny
  • 汇聚 reduce
  • 收集器 toArray collect


常用的几个lambda

下面我们对这几个常用的lambda表达式写几个demo,首先定义公共的Student类:

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
public class Student {

public Student(String stuName, int age, BigDecimal score, int clazz) {
this.stuName = stuName;
this.age = age;
this.score = score;
this.clazz = clazz;
}

private String stuName;
private int age;
private BigDecimal score;
private int clazz;

public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public BigDecimal getScore() {
return score;
}
public void setScore(BigDecimal score) {
this.score = score;
}
public int getClazz() {
return clazz;
}
public void setClazz(int clazz) {
this.clazz = clazz;
}
}

List<Student> studentList = new ArrayList<>();
studentList.add(new Student("黎 明", 20, new BigDecimal(80), 1));
studentList.add(new Student("郭敬明", 22, new BigDecimal(90), 2));
studentList.add(new Student("明 道", 21, new BigDecimal(65.5), 3));
studentList.add(new Student("郭富城", 30, new BigDecimal(90.5), 4));
studentList.add(new Student("刘诗诗", 20, new BigDecimal(75), 1));
studentList.add(new Student("成 龙", 60, new BigDecimal(88), 5));
studentList.add(new Student("郑伊健", 60, new BigDecimal(86), 1));
studentList.add(new Student("刘德华", 40, new BigDecimal(81), 1));
studentList.add(new Student("古天乐", 50, new BigDecimal(83), 2));
studentList.add(new Student("赵文卓", 40, new BigDecimal(84), 2));
studentList.add(new Student("吴奇隆", 30, new BigDecimal(86), 4));
studentList.add(new Student("言承旭", 50, new BigDecimal(68), 1));
studentList.add(new Student("郑伊健", 60, new BigDecimal(86), 1));
studentList.add(new Student("黎 明", 20, new BigDecimal(80), 1));
studentList.add(new Student("李连杰", 65, new BigDecimal(86), 4));
studentList.add(new Student("周润发", 69, new BigDecimal(58), 1));
studentList.add(new Student("徐若萱", 28, new BigDecimal(88), 6));
studentList.add(new Student("许慧欣", 26, new BigDecimal(86), 8));
studentList.add(new Student("陈慧琳", 35, new BigDecimal(64), 1));
studentList.add(new Student("关之琳", 45, new BigDecimal(50), 9));
studentList.add(new Student("温碧霞", 67, new BigDecimal(53), 2));
studentList.add(new Student("林青霞", 22, new BigDecimal(56), 3));
studentList.add(new Student("李嘉欣", 25, new BigDecimal(84), 1));
studentList.add(new Student("彭佳慧", 26, new BigDecimal(82), 5));
studentList.add(new Student("陈紫涵", 39, new BigDecimal(88), 1));
studentList.add(new Student("张韶涵", 41, new BigDecimal(90), 6));
studentList.add(new Student("梁朝伟", 58, new BigDecimal(74), 1));
studentList.add(new Student("梁咏琪", 65, new BigDecimal(82), 7));
studentList.add(new Student("范玮琪", 22, new BigDecimal(83), 1));


forEach


forEach:代表循环当前的list ,下面的例子是循环打印出student的名字

1
2
3

studentList.stream().forEach(x -> System.out.println(x.getStuName()));


filter


根据条件过滤当前的数据,获得分数大于80的学生名称

1
2
3

studentList.stream().filter(t -> t.getScore().compareTo(new BigDecimal(80)) > 0).forEach(x -> System.out.println(x.getStuName()));


distinct、sorted 、group


  1. 去除重复数据

    1
    2
    3

    studentList.stream().distinct().forEach(x -> System.out.println(x.getStuName()));

  2. 单条件排序和多条件排序

    1
    2
    3
    4
    5
    6
       
    studentList.stream().sorted(Comparator.comparing(Student::getScore)).forEach(x -> System.out.println(x.getStuName()));

    //多条件排序
    studentList.stream().sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getStuName)).forEach(x -> System.out.println(x.getStuName()));

  3. group 的使用

    1
    2
    3
        
    System.out.println(studentList.stream().collect(Collectors.groupingBy(x->x.getAge(),Collectors.counting())));


limit、skip

跳过多少,取多少个元素,可以根据当前的数据进行分页

1
2
3
4
5
6
7
8

studentList.stream().skip(10).limit(5).forEach(x -> System.out.println(x.getStuName()));

//具体的分页
int pageIndex=1;
int pageSize=5;
studentList.stream().skip((pageIndex-1)*pageSize).limit(pageSize).forEach(x -> System.out.println(x.getStuName()));


map/flatMap

map是一个转换的工具,提供很多转换的方法,mapToInt,mapToDouble

1
2
3

studentList.stream().map(Student::getScore).forEach(x -> System.out.println(x));

map

上面的结果是输出当前的所有同学的得分。

flatMap是一个可以把子数组的值放到数组里面, 下面的实例是把所有的名字都拆开成一个新的数组

1
2
3
4

studentList.stream().flatMap(x-> Arrays.stream(x.getStuName().split(""))).forEach(x -> System.out.println(x));


flatmap


min、max、count、 average

一组常用的统计函数:

1
2
3
4
5
6

studentList.stream().max(Comparator.comparing(x -> x.getAge())).ifPresent(x-> System.out.println(x.getAge()));
studentList.stream().min(Comparator.comparing(x -> x.getAge())).ifPresent(x-> System.out.println(x.getAge()));
System.out.println(studentList.stream().count());
studentList.stream().mapToDouble(x -> x.getScore().doubleValue()).average().ifPresent(x-> System.out.println(x));

flatmap


anyMatch、noneMatch、 allMatch、 findFirst、 findAny

  • anyMatch: 操作用于判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。

  • noneMatch: 与anyMatch相反。allMatch是判断所有元素是不是都满足表达式。

  • findFirst: 操作用于获取含有Stream中的第一个元素的Optional,如果Stream为空,则返回一个空的Optional。若Stream并未排序,可能返回含有Stream中任意元素的Optional。

  • findAny: 操作用于获取含有Stream中的某个元素的Optional,如果Stream为空,则返回一个空的Optional。由于此操作的行动是不确定的,其会自由的选择Stream中的任何元素。在并行操作中,在同一个Stram中多次调用,可能会不同的结果。在串行调用时,都是获取的第一个元素, 默认的是获取第一个元素,并行是随机的返回。

1
2
3
4
5
6
7
8
9
10
11
12

System.out.println(studentList.stream().anyMatch(r -> r.getStuName().contains("伟")));
System.out.println(studentList.stream().allMatch(r -> r.getStuName().contains("伟")));
System.out.println(studentList.stream().noneMatch(r -> r.getStuName().contains("伟")));
System.out.println(studentList.stream().findFirst());
System.out.println(studentList.stream().findAny());

for (int i=0;i<10;i++)
{
System.out.println(studentList.stream().parallel().findAny().get().getStuName());
}


reduce

对于reduce的使用,应该在js中也有接触到,但也是比较小众的功能,但使用起来功能却非常的强大,先看一个正常的demo:

1
2
3
4
5
6
7
8
9
10

Stream.of(1, 5, 10, 8).reduce((x, y) -> {
System.out.println("x : " + x);
System.out.println("y : " + y);
System.out.println("x+y : " +x);

System.out.println("--------");
return x + y;
});

打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
x : 1
y : 5
x+y : 1
--------
x : 6
y : 10
x+y : 6
--------
x : 16
y : 8
x+y : 16
--------

可以看出:

  1. reduce是一个循环,有两个参数
  2. 第一次执行的时候x是第一个值,y是第二个值。
  3. 在第二次执行的时候,x是上次返回的值,y是第三个值
    ….
    直到循环结束为止。

再修改代码如下:

1
2
3
4
5
6
7
8
9
10
11

//指定了初始值

Stream.of(1, 5, 10, 8).reduce(100,(x, y) -> {
System.out.println("x : " + x);
System.out.println("y : " + y);
System.out.println("x+y : " +x);
System.out.println("--------");
return x + y;
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
x : 100
y : 1
x+y : 100
--------
x : 101
y : 5
x+y : 101
--------
x : 106
y : 10
x+y : 106
--------
x : 116
y : 8
x+y : 116
--------


toArray、collect

toArray和collect是两个收集器,toArray是把数据转换成数组,collect是转成其他的类型。这里就不在讨论了。

1
2
3
 
System.out.println(studentList.stream().collect(Collectors.groupingBy(x->x.getAge(),Collectors.counting())));

Maven教程--快速入门

最近一段时间准备整理下Maven相关的知识,受前面几次野心太大的教训,这次没写一篇就发一篇,不再一直憋着等到后面一起发布。本篇文章是一个使用入门的教程(至于安装和配置,已经有很多教程了,这里就不再赘述了)

什么是Maven

对于这个话题,官方有很多解释,但很多的解释都不是很好理解,既然这里是个人的博客,我就怎么容易理解怎么写。

从简单的角度去理解maven,可以简单的理解成一种项目的管理方式(如同vs中的项目文件,不过vs封装的更好一点),从一个项目的角度来考虑,项目主要包含源代码,资源文件,配置文件他其他的相关引用,如何管理这些文件呢? 我们很自然的可以想到把项目的文件和引用放到一个文本中,当编译器再次加载项目的时候就不用再重新扫描文件目录。

对于这个文本格式,我们很自然的想到用json或者xml格式去存储,这样就很容易明白为什么我们要使用Maven了。 讲到这里也不得不把官方的解释拿过来:

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.

翻译过来就是:

Apache Maven是一个软件项目管理和逻辑工具。基于项目对象模型(POM)的概念, 可以用来管理项目的构建, 信息中心的报告和文件 。

Maven的在windows和Linux下的安装,这里就不多说了,网上有很多图文并茂的教程,这里就不多说了,也不做推荐了

Maven的快速入门

Maven是一个项目管理工具,对于工具来说只有使用的时候才能更好的理解它,这里我们先不将任何概念,先说说如何使用Maven。

使用Maven命令创建一个新的Maven项目:

1
mvn archetype:generate 

在选择项目Number和Maven版本的时候,由于Maven已经直接帮我门选好,我们可以直接按回车确认(1107的quickstar),后面需要我们输入GroupId,ArtifactId和其他的一些相关信息,可以使用如下:

Maven选择项目

在最终确认的时候,我们等待Maven创建完项目,项目创建完成后,我们使用tree命令来看下Maven生成的项目目录。

Maven选择项目

可以看到Maven的quickstart已经给我我们创建好了相关的目录和代码,看下对应生成的代码:

运行Maven Install 命令,把当前的程序安装到本地仓库。运行完成后,我们查看生成后的项目目录结构如下:

Maven选择项目

Maven常用命令

对于maven常用的命令,我们需要对maven的其他只是进行了解。

  • Maven的生命周期

    maven的强大在于他完整的生命周期,这里就不全部列出来所有的周期,挑几个重要的列出来:

    名称 含义
    1. process-resources 复制并处理资源文件,至目标目录,准备打包。
    2. compile 编译项目的源代码。
    3. process-classes 复制并处理资源文件,至目标测试目录。
    4. process-test-resources 复制并处理资源文件,至目标测试目录
    5. test-compile 编译测试源代码
    6. test 框架运行测试
    7. package 打包
    8. install 安装本地仓库
    9. deploy 发布到远程仓库
  • 常用命令的使用

    命令 含义
    mvn archetype:generate 创建maven项目
    mvn compile 编译源代码
    mvn test-compile 编译测试代码
    mvn test 运行应用程序中的单元测试
    mvn site 生成项目相关的网站
    mvn clean 清除目标目录生成结果
    mvn package 生成jar包
    mvn install 安装本地仓库

HaspMap的原理

实现简单的Map


前几天有想法弄懂HashMap的实现的原理,我自己也YY了一个想法去实现一个简单的Map, 代码如下:

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
public class KeyValuePair<K,V> {

public K Key;
public V Value;

public K getKey() {
return Key;
}

public void setKey(K key) {
Key = key;
}

public V getValue() {
return Value;
}

public void setValue(V value) {
Value = value;
}
}

```
然后使用List作为Container对数据进行存储,主体的内部实现原理如下:

```Java
public class MyMap<K, V> {
private List<KeyValuePair<K, V>> map;

public MyMap() {
map = new ArrayList<KeyValuePair<K, V>>();
}

public V put(K k, V v) {
KeyValuePair<K, V> keyValuePair = new KeyValuePair<K, V>();
keyValuePair.setKey(k);
keyValuePair.setValue(v);
map.add(keyValuePair);
return v;
}

public V get(K k) {
for (KeyValuePair pair : map) {
if (pair.getKey().equals(k)) {
return (V) pair.getValue();
}
}
return null;
}
}

虽然也能实现类似的效果,但我们可以看到这个的map的时间复杂度是O(n),当集合数量很大时,则效率可以的非常的糟糕,下面做一个对比的测试:

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
@Test
public void MapTest(){

long start=System.currentTimeMillis();
MyMap<String,String> map =new MyMap();
for (int i=0;i<10000;i++){
map.put("Key"+i,"value"+i);
}
for (int i=0;i<10000;i++){
map.get("Key"+i);
}
long end=System.currentTimeMillis();
System.out.println("耗时:"+(end-start));

start=System.currentTimeMillis();
Map<String,String> hashMap =new HashMap<>();
for (int i=0;i<10000;i++){
hashMap.put("Key"+i,"value"+i);
}
for (int i=0;i<10000;i++){
hashMap.get("Key"+i);
}
end=System.currentTimeMillis();
System.out.println("耗时:"+(end-start));
}
```

运行结果如下:

```cte
耗时:1815
耗时:14
```

整整慢了100多倍!

### HashMap的实现原理
----------------------------

对于上面的代码,我们应该知道性能最慢的是查找对应的key值,对于ArrayList来说,可能插入也是很大的性能消耗。在JDK中使用一个数组来存储key,索引是根据Key的Hash值来确定,而每一个key对应数据单元是一个链表。用图表示效果如下:

![HaspMap的原理](/img/assets/22/01.png)

下面我们JDK的原理进行分析:

#### 存值

1. 首先定义一个数组,其类型是一个Key-Value类型

2. 根据key的Hash值来确定当前的索引

3. 根据索引值来判断当前是否有值,如果当前有值则把当前的值插入当前数据之前

#### 取值

1.根据key的Hash值来确定当前的索引,根据索引来找到链表的首节点

2.遍历链表,找到指定的Key对应的节点,取出当前值



具体的实现代码如下(可以利用上面的代码):

```Java
public class KeyValuePair<K,V> {

public K Key;
public V Value;
public KeyValuePair next;

public KeyValuePair getNext() {
return next;
}

public void setNext(KeyValuePair next) {
this.next = next;
}
public KeyValuePair(){

}
public KeyValuePair(K k, V v){
this.Key=k;
this.Value=v;
}
public K getKey() {
return Key;
}

public void setKey(K key) {
Key = key;
}

public V getValue() {
return Value;
}

public void setValue(V value) {
Value = value;
}
}

HashMap的实现:

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
public class MyHashMap<K, V> {

private int defalutLength = 16;
private int size;
private KeyValuePair<K, V>[] arr;
public MyHashMap() {
arr = new KeyValuePair[defalutLength];
size = 0;
}

public V put(K k, V v) {
int index = findIndex(k);
//todo:find out of index
if (arr[index] == null) {
arr[index] = new KeyValuePair(k, v);
} else {
KeyValuePair tempPair = arr[index];
arr[index] = new KeyValuePair(k, v);
arr[index].setNext(tempPair);
}
size++;
return v;
}

private int findIndex(K key) {
int index=key.hashCode() % defalutLength;
return index>0?index:(-1)*index;
}

public V get(K k) {
int index = findIndex(k);
if (arr[index] == null) {
return null;
}
KeyValuePair<K, V> current = arr[index];
while (current.next != null) {
if (current.getKey().equals(k)) {
return current.getValue();
}
current = current.next;
}
return null;
}
public int size(){
return this.size;
}

}

```

同样我们修改测试的代码:
```Java
@Test
public void MapTest(){

long start=System.currentTimeMillis();
MyMap<String,String> map =new MyMap();
for (int i=0;i<10000;i++){
map.put("Key"+i,"value"+i);
}
for (int i=0;i<10000;i++){
map.get("Key"+i);
}
long end=System.currentTimeMillis();
System.out.println("耗时:"+(end-start));

start=System.currentTimeMillis();
Map<String,String> hashMap =new HashMap<>();
for (int i=0;i<10000;i++){
hashMap.put("Key"+i,"value"+i);
}
for (int i=0;i<10000;i++){
hashMap.get("Key"+i);
}
end=System.currentTimeMillis();
System.out.println("耗时:"+(end-start));



start=System.currentTimeMillis();
MyHashMap<String,String> myhashMap =new MyHashMap<>();
for (int i=0;i<10000;i++){
myhashMap.put("Key"+i,"value"+i);
}
for (int i=0;i<10000;i++){
myhashMap.get("Key"+i);
}
end=System.currentTimeMillis();
System.out.println("耗时:"+(end-start));

}
```

运行结果:

```cte
耗时:2337
耗时:26
耗时:337
```

我们看到我们使用的链表在插入数据的时候进行整理,极大的提高了Map的效率,但离Jdk的性能还有很大的差距。


### 优化散列算法
---------------------
对于Map的查找的性能的瓶颈主要在最后的链表的查找,我们可以把Key的数据进行扩大,让Key分布的更加平均,这样就能减少最后链表迭代次数,实现思路:

1. 添加一个报警百分比,当key的使用率长度大于当前的比例,我们对key的数组进行扩容

2. 扩容后对原来的Key进行重新散列

修改后代码如下:

```Java
public class MyHashMap<K, V> {

private int defalutLength = 16;
private final double defaultAlfa = 0.75;
private int size;
private int arrLength;
private KeyValuePair<K, V>[] arr;

public MyHashMap() {
arr = new KeyValuePair[defalutLength];
size = 0;
arrLength=0;
}

public V put(K k, V v) {
int index = findIndex(k);
//todo:find out of index
if(arrLength>defalutLength*defaultAlfa){
extentArr();
}
if (arr[index] == null) {
arr[index] = new KeyValuePair(k, v);
arrLength++;
} else {
KeyValuePair tempPair = arr[index];
arr[index] = new KeyValuePair(k, v);
arr[index].setNext(tempPair);
}
size++;
return v;
}

private int findIndex(K key) {

int index=key.hashCode() % defalutLength;
return index>0?index:(-1)*index;
}
private void extentArr(){
defalutLength=defalutLength*2;
KeyValuePair<K, V>[] newArr=new KeyValuePair[defalutLength];
for (int i=0;i<defalutLength/2;i++){
if(arr[i]!=null){
int index= findIndex(arr[i].getKey());
newArr[index]=arr[i];
}
}
arr=newArr;
}
public V get(K k) {
int index = findIndex(k);
if (arr[index] == null) {
return null;
}

KeyValuePair<K, V> current = arr[index];
while (current.next != null) {
if (current.getKey().equals(k)) {
return current.getValue();
}
current = current.next;
}
return null;
}
public int size(){
return this.size;
}

}

```

最终测试性能结果如下:
```cte
耗时:2263
耗时:23
耗时:33

性能已经很接近了,至于为什么有差异,可能jdk有其它更多的优化(比如当链表长度大于8时,使用红黑树),但本文就讨论到这里。

Java中的容器

容器的种类

为什么要使用容器? 因为数组不能够满足日常的开发需求,数组有以下弊端:

  1. 长度难以扩充
  2. 数据的类型必须相同
  3. 数组无法获得有多少个真实的数据,只能获得数组的长度。

在Java中有常用的三种类型的容器,分别是List 、Map、Set,基于这个三个基本的类型,派生出很多其它的类型,具体关系如下:

Relation

三者的区别:

  • Set(集):与list都是有Collection类的派生出来, 分辨各个元素的标识是HashCode,所以元素不能有重复
  • List(列表):是一个有序的列表,元素如果有重复,也会一一列出来。
  • Map(映射): Map是我们常说的键值对,有key和Value两个元素

使用方法:

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
 @Test
public void ContainerTest() {
String string[] = {"i", "am", "am", "xiao", "ming"};

List<String> list = new ArrayList<String>();
for (String s : string) {
list.add(s);
}
System.out.println("List执行结果:");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

//
Set<String> set = new HashSet<String>();
for (String s : string) {
set.add(s);
}
Iterator iterator = set.iterator();
System.out.println("===================");
System.out.println("Set 执行结果:");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

```

运行结果:

```cte
List执行结果:
i
am
am
xiao
ming
===================
Set 执行结果:
ming
xiao
i
am

三者的区别可以表示如下图:

ListMapSet

各个容器的说明和使用


List


ArrayList

ArrayList是List一个派生类,非线安全,是基于Object数组实现的可动态扩展的容器,在调用Add的时候会判断当前的长度是否已经超过了Size.对应的Add方法:

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
  public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}

LinkList与ArrayList区别是LinkList 是基于链表的结构设计 ,插入和删除的性能要高于ArrayList,查询的效率低于LinkList,使用方法基本一致,也是非线安全,下面看下性能测试代码:

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
 @Test
public void ListAdd() {
System.out.println("ArrayList ADD耗时:" + AddList(new ArrayList()));
System.out.println("LinkedList ADD耗时:" + AddList(new LinkedList()));

//测试Edit
final int N = 50000;
Integer vals[] = new Integer[N];
Random r = new Random();
for (int i = 0, currval = 0; i < N; i++) {
currval += r.nextInt(100) + 1;
vals[i] = new Integer(currval);
}
List lst = Arrays.asList(vals);
System.out.println("ArrayList Search耗时:" + SearchList(new ArrayList(lst)));
System.out.println("LinkedList Search耗时:" + SearchList(new LinkedList(lst)));
}
long AddList(List list) {
final int N = 50000;
long start = System.currentTimeMillis();
Object o = new Object();
for (int i = 0; i < N; i++)
list.add(0, o);
return System.currentTimeMillis() - start;
}

long SearchList(List lst) {
final int N = 50000;
long start = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
int index = Collections.binarySearch(lst, lst.get(i));
if (index != i)
System.out.println("***错误***");
}
return System.currentTimeMillis() - start;
}
```

运行结果:

``` cte
ArrayList ADD耗时:396
LinkedList ADD耗时:9

ArrayList Search耗时:20
LinkedList Search耗时:8330
Vector

比arraylist多了个同步化机制(线程安全),用法相同,但效率比较低,不建议使用。

Map


HashMap 和 HashTable

二者在使用上功能差不多,区别是HashMap是线程不安全,允许多线程去同时访问,允许插入空值。 而HashTable是相反的,对于HapMap的使用,可以参考下面代码:

1
2
3
4
5
6
7
8
9
10
11
Map map=new HashMap();
map.put("key","abc");
map.put("key1","abc1");
map.put("key2","abc2");
System.out.println(map.get("key"));

map.remove("key");
System.out.println(map.values());
System.out.println(map.keySet());
System.out.println(map.entrySet());

运行结果如下:

1
2
3
4
5
abc
[abc1, abc2]
[key1, key2]
[key1=abc1, key2=abc2]

TreeMap

是一个有顺序的HaspMap

手工实现容器ArrayList

根据上面的分析,我们可以手工实现一个ArrayList 代码如下:

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
public class MyArrayList {

private Object[] _arr;
private int _size;

public MyArrayList() {
_arr = new Object[10];
this._size = 0;
}

public void Add(Object obj) {
if (this._size >= this._arr.length) {
Object[] newArray = new Object[2 * this._size + 1];

for (int i = 0; i < this._size; i++) {
newArray[i] = this._arr[i];
}
this._arr = newArray;
this._arr[this._size] = obj;
this._size++;
} else {
this._arr[this._size] = obj;
this._size++;
}
}

public int Size() {
return this._size;
}

public Object get(int index) {
if (index > this._size) {
throw new IndexOutOfBoundsException("索引超出界限");
}
return this._arr[index];
}
}

使用方法:


@Test
public void PrintMyArr(){
    MyArrayList list=new MyArrayList();
    for (int i=0;i<10000;i++){
        list.Add(i);
    }

    System.out.println(list.Size());
    System.out.println(list.get(503));
    System.out.println(list);
}
 

Java枚举类型使用

在编程中,常常遇到多种类型、多种状态的情况,对于这种可以事先预知的业务我们常常有两种表示方法:

  • 使用DB管理,优点是方便在线维护,缺点是每次加载都要读取数据库,加载的性能取决于数据库的压力 。

  • 使用枚举类型,优点是加载速度依赖于应用服务器,缺点是修改比较麻烦,每次加类型都需要发布代码。

对于Java枚举类型的使用,我们可以总结为以下几个方面:整型值,字符串字段和字符串的相关描述,下面我们就讨论如何方便的在这几个类型中相关转换,对于所有的类型转换可以总结如下:

1
2
3
4
5
6
7
int --> Enum 
Enum--> int
String -->Enum
Enum-->String
Enum-->描述
int -->描述
String -->描述

下面我们对于上面的7种描述来做出对应的转换方法,首先定义一个枚举类型:

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
    public enum EnumDemo {
fail,
success,
unknow;
}
```

为了能够保证相关的value和值能够保存下来,我们需要对枚举类型改成如下:

```Java
public enum EnumDemo {
fail(0, "失败"),
success(1, "成功"),
unknow(2, "未知");

private int value;
private String name;

EnumDemo(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return this.value;
}
public String getName() {
return this.name;
}
}
```


相关代码如下:

```Java
@Test
public void TestEnum(){
int val=1;
EnumDemo intEnum=EnumDemo.values()[val];//整型转Enum
String enumStr= intEnum.toString();//Enum转字符串
String enumAnno=intEnum.getName();//Enum转描述

System.out.println(enumStr);
System.out.println(enumAnno);


String str="success";
EnumDemo strEnum=EnumDemo.values()[val];//字符转Enum
int enumVal=strEnum.getValue();//字符转int
String enumValAnno=strEnum.getName();//Enum转描述

System.out.println(enumVal);
System.out.println(enumValAnno);
}

运行结果如下:

1
2
3
4
success
成功
1
成功

Java反射和注解

反射

反射是指在运行的状态,对于任意一个类,都能够知道类里面的所有的属性和方法,并能够进行属性的赋值和方法的调用 。 在Java中使用Java.lang下面的Class来表示**类型的”类” ** ,在JDK中定义接口如下

Java反射

其中T 表示运行时类的类型,如果不知道类型可以使用Class<?>,Class表示的实例表示正在运行的 Java 应用程序中的类(包含枚举) 和接口 , 所有的反射出来的结果都共享一个基类Class。

获得类型、方法、属性和构造器

在Java中有三种方法可以反射定制类的Class(以String类型为例):

1
2
3
4
5
6
1. 通过Class.from("Java.lang.String")    

2. 通过String.class

3. 通过类的实例对像中的getClass()方法 :"abc".getClass()

为了演示反射的功能,我们首先定义一个类型:

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
class Person
{
private String userName;
private String userCode;
public String Sex;//字段

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getUserCode() {
return userCode;
}

public void setUserCode(String userCode) {
this.userCode = userCode;
}

public String GetUserNameWithUserCode(){
return this.getUserName()+"_"+getUserCode();
}

public String GetUserNameWithUserCode(String prefix){
return prefix+"_"+ this.getUserName()+"_"+getUserCode();
}

public static String GetClassName(String prefix){
return prefix+"_Person";
}
}

获得Person类中的方法、属性和构造器,代码如下:

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
try {
Class<?> clazz = Class.forName("Person");

System.out.println("--------------Person的方法如下----------------");
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}


System.out.println("--------------Person字段如下----------------");
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.println(f);
}

System.out.println("--------------Person构造函数如下----------------");
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
} catch (Exception e) {
e.printStackTrace();
}
```

运行结果:

```cte
--------------Person的方法如下----------------
public Java.lang.String Person.getUserName()
public Java.lang.String Person.GetUserNameWithUserCode()
public Java.lang.String Person.GetUserNameWithUserCode(Java.lang.String)
public void Person.setUserName(Java.lang.String)
public Java.lang.String Person.getUserCode()
public void Person.setUserCode(Java.lang.String)
public static Java.lang.String Person.GetClassName(Java.lang.String)
public final void Java.lang.Object.wait() throws Java.lang.InterruptedException
public final void Java.lang.Object.wait(long,int) throws Java.lang.InterruptedException
public final native void Java.lang.Object.wait(long) throws Java.lang.InterruptedException
public boolean Java.lang.Object.equals(Java.lang.Object)
public Java.lang.String Java.lang.Object.toString()
public native int Java.lang.Object.hashCode()
public final native Java.lang.Class Java.lang.Object.getClass()
public final native void Java.lang.Object.notify()
public final native void Java.lang.Object.notifyAll()
--------------Person字段如下----------------
public Java.lang.String Person.Sex
--------------Person构造函数如下----------------
Person()

```

#### 方法的调用和属性的赋值

上面的代码我们能够获得对应的属性和方法,下面就解决如何去调用当前方法和属性进行赋值:

```Java
try {
Class clazz =Class.forName("Person");
Object obj=clazz.newInstance();//创建对象

//set方法的获得
Method setUserName=clazz.getMethod("setUserName",String.class);
Method setUserCode=clazz.getMethod("setUserCode",String.class);
//set方法的调用
setUserName.invoke(obj,"fuwei");
setUserCode.invoke(obj,"F0001");

//一般方法的获得和调用
Method m1=clazz.getMethod("GetUserNameWithUserCode");
String s1= m1.invoke(obj,null).toString();

Method m2=clazz.getMethod("GetUserNameWithUserCode",String.class);
String s2= m2.invoke(obj,new Object[]{"Test"}).toString();

//静态方法的调用
Method m3=clazz.getMethod("GetClassName",String.class);
String s3= m3.invoke(null,new Object[]{"Test"}).toString();

System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
catch (Exception e) {
e.printStackTrace();
}

```

输出结果:

```cte
fuwei_F0001
Test_fuwei_F0001
Test_Person

使用反射实现动态代理

为了更好的理解动态代理,首先要理解静态代理的逻辑,具体的实现示意图:

动态代理

具体实现类代码如下:

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
interface ProxyInterface {
public void doSomething();
}

//被代理者
class RealObj implements ProxyInterface {
@Override
public void doSomething() {
System.out.println("doSomething");
}

}

//代理者
class ProxyObj implements ProxyInterface {
@Override
public void doSomething() {
System.out.println("before Exector");
new RealObj().doSomething();
System.out.println("after Exector");
}

}


对应的调用代码:

1
2
3
4
5
6
7
8
9
10
11

@Test
public void TestProxy(){
CallMethod(new ProxyObj());

}

public void CallMethod(ProxyInterface pi){
pi.doSomething();
}

运行结果:

1
2
3
4
before Exector
doSomething
after Exector

在静态的代理中,可以看到是在代理类的内部使用了真实类的实例,来实现代理的功能,但这样就只能代理指定的类的类型,丧失了灵活性。如果我们能够在代理的时候把数据传入到代理类中,就可以动态的实现类的代理。 代码实现原理如下:

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
interface ProxyInterface {
public void doSomething();
}

//被代理者
class RealObj implements ProxyInterface {
@Override
public void doSomething() {
System.out.println("doSomething");
}

}

//代理者
class ProxyObj implements InvocationHandler {

private Object subject;
public ProxyObj(Object subject)
{
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("Before Exector");
Object obj = method.invoke(subject, args);
System.out.println("After Exector");

return obj;
}
}

调用的实现:

1
2
3
4
5
6
7
8
9
10
11

public void TestProxy()
{
RealObj real = new RealObj();
ProxyInterface proxy = (ProxyInterface) Proxy.newProxyInstance(
ProxyInterface.class.getClassLoader(),
new Class[]{ProxyInterface.class},
new ProxyObj(real));
proxy.doSomething();
}

对于这个实现的原理可以参考这篇博客细说JDK动态代理的实现原理 {:target=_blank}, 生成了一个继承自Proxy和实现了ProxyInterface方法的一个对象,具体的示意为:

1
2
3
4
class proxy extends Proxy implements HelloWorld{
....
}

注解

Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容,常用的注解有可以参考:Java注释Override、Deprecated、SuppressWarnings详解

自定义注解

注解的大多使用情况都是结合反射,在Spring框架中也有很多都是使用反射+注解的方法来实现,下面为了更深入了解注解,我们可以自定义一个注解,注解在Java中的实现很简单:

1
2
3
4
5
6
public @interface MyAnno   
{

}


只需要这样定义就可以直接使用这个注解 ,但这个是没有任何的实际意义。在这个注解中,可以添加对应的内部方法:

1
2
3
4
5
6
7
8

public @interface MyAnno
{

String value();
}


在使用的时候,我们就可以对value进行赋值:

1
2
3
4
5
6
@MyAnno(value = "test")
public void TestReflect()
{

}

对于上面的代码,注解没有干涉内部代码的逻辑,但也没有起任何的作用。对于注解的使用,我们可以根据上面说到反射来实现我们的目的。比如,我们想实现一个注解为test的方法调用,可以先获得一个方法的所有注解,然后根据注解的value值来判断是否调用 ,下面是获得所有注解的方法:

1
2
3
4
5
6
7
8
9
10

Method[] methods = clazz.getMethods();
for (Method m : methods) {
Annotation[] ans= m.getDeclaredAnnotations();
for (Annotation an : ans)
{
System.out.println(an);
}
}

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×