add java
parent
0f1d690937
commit
5768a06296
|
@ -0,0 +1,99 @@
|
|||
今天在排除代码中的Bug的时候,在浮点数运算过程中遇到了NAN与INFINITY的问题。特此记录一下。
|
||||
首先明确一点的是,java浮点数中有两个特殊情况:NAN,INFINITY
|
||||
|
||||
## 1.NAN
|
||||
NAN是一个特殊的值。在JDK中,NAN是这么定义的:
|
||||
|
||||
```
|
||||
/**
|
||||
* A constant holding a Not-a-Number (NaN) value of type
|
||||
* {@code double}. It is equivalent to the value returned by
|
||||
* {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
|
||||
*/
|
||||
public static final double NaN = 0.0d / 0.0;
|
||||
```
|
||||
|
||||
特意将注释也copy下来。相信加上注释,同学们就都明白是什么意思了。Not-a-Number准确道出了NAN的含义。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void testNan() {
|
||||
double NaN1 = Double.NaN;
|
||||
double NaN2 = 0.0 / 0.0;
|
||||
System.out.println(Double.isNaN(NaN1)); //true
|
||||
System.out.println(Double.isNaN(NaN2)); //true
|
||||
System.out.println(NaN1 == NaN1); //false
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
NAN表示非数字,它与任何值都不相等,甚至不等于它自己,所以要判断一个数是否为NAN要用isNAN方法。
|
||||
|
||||
## 2.INFINITY
|
||||
INFINITY主要是为了解决除数为0的情况。稍微有点数学基础的同学,都应该明白无限这个概念。
|
||||
|
||||
```
|
||||
/**
|
||||
* A constant holding the positive infinity of type
|
||||
* {@code double}. It is equal to the value returned by
|
||||
* {@code Double.longBitsToDouble(0x7ff0000000000000L)}.
|
||||
*/
|
||||
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
|
||||
|
||||
/**
|
||||
* A constant holding the negative infinity of type
|
||||
* {@code double}. It is equal to the value returned by
|
||||
* {@code Double.longBitsToDouble(0xfff0000000000000L)}.
|
||||
*/
|
||||
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
/**
|
||||
* A constant holding the positive infinity of type
|
||||
* {@code float}. It is equal to the value returned by
|
||||
* {@code Float.intBitsToFloat(0x7f800000)}.
|
||||
*/
|
||||
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
|
||||
|
||||
/**
|
||||
* A constant holding the negative infinity of type
|
||||
* {@code float}. It is equal to the value returned by
|
||||
* {@code Float.intBitsToFloat(0xff800000)}.
|
||||
*/
|
||||
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
|
||||
```
|
||||
|
||||
这是JDK中的相关定义。很容易看出来,double与float中都有INFINITY的相关定义。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void testInfinity() {
|
||||
double Inf1 = Double.POSITIVE_INFINITY;
|
||||
double Inf2 = Double.NEGATIVE_INFINITY;
|
||||
|
||||
float Inf3 = Float.POSITIVE_INFINITY;
|
||||
float Inf4 = Float.NEGATIVE_INFINITY;
|
||||
|
||||
System.out.println(Double.isInfinite(Inf1)); //true
|
||||
System.out.println(Float.isInfinite(Inf3)); //true
|
||||
|
||||
System.out.println(Inf1 == Inf3); //true
|
||||
System.out.println(Inf2 == Inf4); //true
|
||||
|
||||
System.out.println(Inf1 * 0); //NaN
|
||||
|
||||
System.out.println(Inf1 + 1); //Infinity
|
||||
System.out.println(Inf1 * 0.4); //Infinity
|
||||
System.out.println(Inf1 / 0); //Infinity
|
||||
}
|
||||
```
|
||||
|
||||
从测试代码中,可以得出如下结论:
|
||||
1.double或者float判断是不是INFINITY都使用isInfinite方法。
|
||||
2.double中的INFINITY与float中的INFINITY是相等的。
|
||||
3.INFINITY乘以0得到NAN。
|
||||
4.INFINITY做除了乘以0意外的任何四则运算,得到的结果仍然是INFINITY。
|
||||
|
||||
第三点跟第四点,结果INFINITY的数学性质,很容易理解。
|
|
@ -0,0 +1,287 @@
|
|||
## 1.枚举类 (enum)
|
||||
1.在某些情况下,一个类的对象时有限且固定的,如季节类,它只有春夏秋冬4个对象这种实例有限且固定的类,在 Java 中被称为枚举类;
|
||||
2.在 Java 中使用 enum 关键字来定义枚举类,其地位与 class、interface 相同;
|
||||
3.枚举类是一种特殊的类,它和普通的类一样,有自己的成员变量、成员方法、构造器 (只能使用 private 访问修饰符,所以无法从外部调用构造器,构造器只在构造枚举值时被调用);
|
||||
4.一个 Java 源文件中最多只能有一个 public 类型的枚举类,且该 Java 源文件的名字也必须和该枚举类的类名相同,这点和类是相同的;
|
||||
5.使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口;
|
||||
6.所有的枚举值都是 public static final 的,且非抽象的枚举类不能再派生子类;
|
||||
7.枚举类的所有实例(枚举值)必须在枚举类的第一行显式地列出,否则这个枚举类将永远不能产生实例。列出这些实例(枚举值)时,系统会自动添加 public static final 修饰,无需程序员显式添加。
|
||||
|
||||
## 2.枚举类的使用
|
||||
|
||||
### 定义枚举类
|
||||
|
||||
```
|
||||
// 定义一个星期的枚举类
|
||||
public enum WeekEnum {
|
||||
// 在第一行显式地列出7个枚举实例(枚举值),系统会自动添加 public static final 修饰
|
||||
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
|
||||
}
|
||||
```
|
||||
|
||||
### 枚举类的成员变量、成员方法、构造器
|
||||
|
||||
```
|
||||
package enumtest;
|
||||
|
||||
public enum WeekEnum {
|
||||
|
||||
// 因为已经定义了带参数的构造器,所以在列出枚举值时必须传入对应的参数
|
||||
SUNDAY("星期日"), MONDAY("星期一"), TUESDAY("星期二"), WEDNESDAY("星期三"),
|
||||
THURSDAY("星期四"), FRIDAY("星期五"), SATURDAY("星期六");
|
||||
|
||||
// 定义一个 private 修饰的实例变量
|
||||
private String date;
|
||||
|
||||
// 定义一个带参数的构造器,枚举类的构造器只能使用 private 修饰
|
||||
private WeekEnum(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
// 定义 get set 方法
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 枚举类中的常用方法
|
||||
1.int compareTo(E o): 该方法用于与制定枚举对象比较顺序,同一个枚举实例只能与相同类型的枚举实例比较。如果该枚举对象位于指定枚举对象之后,则返回正整数;反之返回负整数;否则返回零;
|
||||
|
||||
```
|
||||
public class Test01 {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(WeekEnum.FRIDAY.compareTo(WeekEnum.MONDAY));
|
||||
System.out.println(WeekEnum.FRIDAY.compareTo(WeekEnum.SUNDAY));
|
||||
System.out.println(WeekEnum.FRIDAY.compareTo(WeekEnum.SATURDAY));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
运行结果:
|
||||
4
|
||||
5
|
||||
-1
|
||||
|
||||
2.String name(): 返回此枚举实例的名称,即枚举值 ;
|
||||
|
||||
3.static values(): 返回一个包含全部枚举值的数组,可以用来遍历所有枚举值;
|
||||
|
||||
```
|
||||
// 没有重写 toString 方法
|
||||
for (WeekEnum we : WeekEnum.values()) {
|
||||
System.out.println(we);
|
||||
}
|
||||
```
|
||||
|
||||
运行结果:
|
||||
SUNDAY
|
||||
MONDAY
|
||||
TUESDAY
|
||||
WEDNESDAY
|
||||
THURSDAY
|
||||
FRIDAY
|
||||
SATURDAY
|
||||
|
||||
4.String toString(): 返回枚举值的名称,与 name 方法类似,更常用;
|
||||
|
||||
```
|
||||
// 定义一个星期的枚举类
|
||||
public enum WeekEnum {
|
||||
|
||||
// 因为已经定义了带参数的构造器,所以在列出枚举值时必须传入对应的参数
|
||||
SUNDAY("星期日"), MONDAY("星期一"), TUESDAY("星期二"), WEDNESDAY("星期三"),
|
||||
THURSDAY("星期四"), FRIDAY("星期五"), SATURDAY("星期六");
|
||||
|
||||
// 定义一个 private 修饰的实例变量
|
||||
private String date;
|
||||
|
||||
// 定义一个带参数的构造器,枚举类的构造器只能使用 private 修饰
|
||||
private WeekEnum(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
// 定义 get set 方法
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
// 重写 toString() 方法
|
||||
@Override
|
||||
public String toString(){
|
||||
return date;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// 重写了 toString 方法
|
||||
for (WeekEnum we : WeekEnum.values()) {
|
||||
System.out.println(we);
|
||||
}
|
||||
```
|
||||
|
||||
运行结果:
|
||||
星期日
|
||||
星期一
|
||||
星期二
|
||||
星期三
|
||||
星期四
|
||||
星期五
|
||||
星期六
|
||||
|
||||
结合上面3.4点,可以看到,重写 toString 方法前后所返回的枚举值不同!
|
||||
|
||||
5.int ordinal(): 返回枚举值在枚举类中的索引值(从0开始),即枚举值在枚举声明中的顺序,这个顺序根据枚举值声明的顺序而定;
|
||||
|
||||
```
|
||||
System.out.println(WeekEnum.SUNDAY.ordinal());
|
||||
System.out.println(WeekEnum.FRIDAY.ordinal());
|
||||
```
|
||||
|
||||
运行结果:
|
||||
0
|
||||
5
|
||||
|
||||
6.static valueOf(): 返回带指定名称的指定枚举类型的枚举常量,名称必须与在此类型中声明枚举常量所用的标识符完全匹配(不允许使用额外的空白字符)。这个方法与toString相对应,因此重写 toString() 方法,一定要重写 valueOf() 方法(我们可以重写 toString() 方法,但不能自己重写 valueOf() 方法,当我们重写 toString() 方法时,valueOf() 方法会自动重写,不用我们理会。);
|
||||
|
||||
```
|
||||
public class Test01 {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(WeekEnum.valueOf(WeekEnum.class, "MONDAY"));
|
||||
System.out.println(WeekEnum.valueOf(WeekEnum.class, "FRIDAY"));
|
||||
System.out.println(WeekEnum.valueOf(WeekEnum.class, "SUNDAY"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
运行结果:
|
||||
MONDAY
|
||||
FRIDAY
|
||||
SUNDAY
|
||||
|
||||
7.boolean equals()方法: 比较两个枚举类对象的引用。
|
||||
|
||||
使用枚举类实现接口
|
||||
与普通类一样,枚举类也可以实现一个或多个接口。枚举类实现接口时,同样要实现
|
||||
该接口的所有方法。
|
||||
|
||||
```
|
||||
public interface GenderDescription {
|
||||
|
||||
public void info();
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
上面定义了一个接口,该接口有一个 info() 方法,凡是实现该接口的类都需要实现该方法。
|
||||
|
||||
```
|
||||
public enum Gender implements GenderDescription {
|
||||
|
||||
MALE,FEMALE;
|
||||
|
||||
@Override
|
||||
public void info() {
|
||||
System.out.println("这是一个用于定义性别的枚举类");
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
public class Test02 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Gender.MALE.info();
|
||||
Gender.FEMALE.info();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
运行结果:
|
||||
这是一个用于定义性别的枚举类
|
||||
这是一个用于定义性别的枚举类
|
||||
|
||||
## 3.包含抽象方法的枚举类
|
||||
定义一个 Operation 枚举类,有4个枚举值PLUS、MINUS、TIMES、DIVIDE,分别代表加、减、乘、除,该枚举类有一个 calculate() 方法,用于完成计算。
|
||||
|
||||
```
|
||||
public enum Operation {
|
||||
|
||||
// 用于执行加法运算
|
||||
PLUS { // 花括号部分其实是一个匿名内部子类
|
||||
|
||||
@Override
|
||||
public double calculate(double x, double y) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// 用于执行减法运算
|
||||
MINUS { // 花括号部分其实是一个匿名内部子类
|
||||
|
||||
@Override
|
||||
public double calculate(double x, double y) {
|
||||
// TODO Auto-generated method stub
|
||||
return x - y;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// 用于执行乘法运算
|
||||
TIMES { // 花括号部分其实是一个匿名内部子类
|
||||
|
||||
@Override
|
||||
public double calculate(double x, double y) {
|
||||
return x * y;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// 用于执行除法运算
|
||||
DIVIDE { // 花括号部分其实是一个匿名内部子类
|
||||
|
||||
@Override
|
||||
public double calculate(double x, double y) {
|
||||
return x / y;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//为该枚举类定义一个抽象方法,枚举类中所有的枚举值都必须实现这个方法
|
||||
public abstract double calculate(double x, double y);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
public class Test03 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("6 + 2 = " + Operation.PLUS.calculate(6, 3));
|
||||
System.out.println("6 - 2 = " + Operation.MINUS.calculate(6, 2));
|
||||
System.out.println("6 * 2 = " + Operation.TIMES.calculate(6, 2));
|
||||
System.out.println("6 / 2 = " + Operation.DIVIDE.calculate(6, 2));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
|
||||
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/enum/1.png)
|
||||
|
||||
|
||||
原文链接地址:
|
||||
http://www.jianshu.com/p/46dbd930f6a2
|
|
@ -0,0 +1,196 @@
|
|||
Java8发布已经有一段时间了,这次发布的改动比较大,很多人将这次改动与Java5的升级相提并论。Java8其中一个很重要的新特性就是lambda表达式,允许我们将行为传到函数中。
|
||||
想想看,在Java8之前我们想要将行为传入函数,仅有的选择就是匿名内部类。Java8发布以后,lambda表达式将大量替代匿名内部类的使用,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。尤其是对于做数据的同学来说,当习惯使用类似scala之类的函数式编程语言以后,体会将更加深刻。现在我们就来看看Java8中lambda表达式的一些常见写法。
|
||||
|
||||
|
||||
## 1.替代匿名内部类
|
||||
毫无疑问,lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大,用()->就可以代替整个匿名内部类!请看代码:
|
||||
|
||||
如果使用匿名内部类:
|
||||
```
|
||||
@Test
|
||||
public void oldRunable() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("The old runable now is using!");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
```
|
||||
|
||||
而如果使用lambda表达式:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void runable() {
|
||||
new Thread(() -> System.out.println("It's a lambda function!")).start();
|
||||
}
|
||||
```
|
||||
|
||||
最后的输出:
|
||||
|
||||
```
|
||||
The old runable now is using!
|
||||
It's a lambda function!
|
||||
```
|
||||
|
||||
是不是强大到可怕?是不是简单到可怕?是不是清晰明了重点突出到可怕?这就是lambda表达式的可怕之处,用极少的代码完成了之前一个类做的事情!
|
||||
|
||||
## 2.使用lambda表达式对集合进行迭代
|
||||
Java的集合类是日常开发中经常用到的,甚至说没有哪个java代码中没有使用到集合类。。。而对集合类最常见的操作就是进行迭代遍历了。请看对比:
|
||||
|
||||
|
||||
```
|
||||
@Test
|
||||
public void iterTest() {
|
||||
List<String> languages = Arrays.asList("java","scala","python");
|
||||
//before java8
|
||||
for(String each:languages) {
|
||||
System.out.println(each);
|
||||
}
|
||||
//after java8
|
||||
languages.forEach(x -> System.out.println(x));
|
||||
languages.forEach(System.out::println);
|
||||
}
|
||||
```
|
||||
|
||||
如果熟悉scala的同学,肯定对forEach不陌生。它可以迭代集合中所有的对象,并且将lambda表达式带入其中。
|
||||
|
||||
```
|
||||
languages.forEach(System.out::println);
|
||||
```
|
||||
|
||||
这一行看起来有点像c++里面作用域解析的写法,在这里也是可以的。
|
||||
|
||||
## 3.用lambda表达式实现map
|
||||
一提到函数式编程,一提到lambda表达式,怎么能不提map。。。没错,java8肯定也是支持的。请看示例代码:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void mapTest() {
|
||||
List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
|
||||
cost.stream().map(x -> x + x*0.05).forEach(x -> System.out.println(x));
|
||||
}
|
||||
```
|
||||
|
||||
最后的输出结果:
|
||||
|
||||
```
|
||||
10.5
|
||||
21.0
|
||||
31.5
|
||||
```
|
||||
|
||||
map函数可以说是函数式编程里最重要的一个方法了。map的作用是将一个对象变换为另外一个。在我们的例子中,就是通过map方法将cost增加了0,05倍的大小然后输出。
|
||||
|
||||
## 4.用lambda表达式实现map与reduce
|
||||
既然提到了map,又怎能不提到reduce。reduce与map一样,也是函数式编程里最重要的几个方法之一。。。map的作用是将一个对象变为另外一个,而reduce实现的则是将所有值合并为一个,请看:
|
||||
|
||||
|
||||
```
|
||||
@Test
|
||||
public void mapReduceTest() {
|
||||
List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
|
||||
double allCost = cost.stream().map(x -> x+x*0.05).reduce((sum,x) -> sum + x).get();
|
||||
System.out.println(allCost);
|
||||
}
|
||||
```
|
||||
|
||||
最终的结果为:
|
||||
|
||||
```
|
||||
63.0
|
||||
```
|
||||
|
||||
如果我们用for循环来做这件事情:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void sumTest() {
|
||||
List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
|
||||
double sum = 0;
|
||||
for(double each:cost) {
|
||||
each += each * 0.05;
|
||||
sum += each;
|
||||
}
|
||||
System.out.println(sum);
|
||||
}
|
||||
```
|
||||
|
||||
相信用map+reduce+lambda表达式的写法高出不止一个level。
|
||||
|
||||
## 5.filter操作
|
||||
filter也是我们经常使用的一个操作。在操作集合的时候,经常需要从原始的集合中过滤掉一部分元素。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void filterTest() {
|
||||
List<Double> cost = Arrays.asList(10.0, 20.0,30.0,40.0);
|
||||
List<Double> filteredCost = cost.stream().filter(x -> x > 25.0).collect(Collectors.toList());
|
||||
filteredCost.forEach(x -> System.out.println(x));
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
最后的结果:
|
||||
|
||||
```
|
||||
30.0
|
||||
40.0
|
||||
```
|
||||
|
||||
将java写出了python或者scala的感觉有没有!是不是帅到爆!
|
||||
|
||||
## 6.与函数式接口Predicate配合
|
||||
除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。Predicate接口非常适用于做过滤。
|
||||
|
||||
```
|
||||
public static void filterTest(List<String> languages, Predicate<String> condition) {
|
||||
languages.stream().filter(x -> condition.test(x)).forEach(x -> System.out.println(x + " "));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<String> languages = Arrays.asList("Java","Python","scala","Shell","R");
|
||||
System.out.println("Language starts with J: ");
|
||||
filterTest(languages,x -> x.startsWith("J"));
|
||||
System.out.println("\nLanguage ends with a: ");
|
||||
filterTest(languages,x -> x.endsWith("a"));
|
||||
System.out.println("\nAll languages: ");
|
||||
filterTest(languages,x -> true);
|
||||
System.out.println("\nNo languages: ");
|
||||
filterTest(languages,x -> false);
|
||||
System.out.println("\nLanguage length bigger three: ");
|
||||
filterTest(languages,x -> x.length() > 4);
|
||||
}
|
||||
```
|
||||
|
||||
最后的输出结果:
|
||||
|
||||
```
|
||||
Language starts with J:
|
||||
Java
|
||||
|
||||
Language ends with a:
|
||||
Java
|
||||
scala
|
||||
|
||||
All languages:
|
||||
Java
|
||||
Python
|
||||
scala
|
||||
Shell
|
||||
R
|
||||
|
||||
No languages:
|
||||
|
||||
Language length bigger three:
|
||||
Python
|
||||
scala
|
||||
Shell
|
||||
|
||||
```
|
||||
|
||||
可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码,这也是lambda表达式的魔力!
|
||||
|
||||
参考文档:
|
||||
1.http://www.importnew.com/16436.html
|
|
@ -0,0 +1,56 @@
|
|||
## 1.先名词解释吧:
|
||||
DAO = Data Access Object = 数据存取对象
|
||||
|
||||
Service = 服务
|
||||
|
||||
Controller = 控制器
|
||||
|
||||
Util = 工具
|
||||
|
||||
Model = 模型
|
||||
|
||||
首先,一个代码是不是有完善的结构,和是不是有上面这些东西没有什么关系,只是通常来说,我们做一个大项目会把项目分解成很多不不同的模块(Module),然后根据用途和角色,我们对这些模块有一个通用的命名规则,这也就是上面这些英文单词的来历。所以,请一定记住,项目中是否包含这些模块或者单词,和你的项目结构是否完善一毛钱关系没有。但是当你的项目结构相对完善的时候,你会发现有这样一些角色的存在。
|
||||
|
||||
接下来一个个的来详细讨论一下这个东西是如何出现的:
|
||||
|
||||
## 2.DAO
|
||||
DAO,数据存取对象。通常我们会遇到很多要和数据库打交道的场景,如果为每一个场景都去写一些SQL语句,会让我们代码变得很奇怪,我们希望我们的代码比较干净整洁,那么一个很容易想到的方案就是把数据库封装一下,让我们和数据库的交道看起来比较像和一个对象打交道。这个对象通常就是DAO,当我们操作这个对象的时候,这个对象会自动的产生SQL语句来和数据库打交道,而我们只需要和DAO打交道就可以了。
|
||||
|
||||
当然,从本质上来说,DAO并不需要和数据库有什么必然的联系,DAO只是数据存取对象的缩写,所以只要是把数据持久化包装成一个对象的访问(读写),这种对象都可以被称之为DAO,譬如,用JSON格式存到硬盘上。
|
||||
|
||||
## 3.Service
|
||||
Service,我们有时候会需要一些相对独立,与业务系统没啥关系的功能。但不是所有的功能都可以做成一个服务,服务是一个相对独立的功能模块,完成一些指定的工作,这些工作高度抽象和通用。一个典型的服务像是数据库服务、缓存服务、文件存储服务、身份验证服务、消息队列服务等。
|
||||
|
||||
关系型数据库服务可以视为是一个接收SQL语句并给出一个查询结果的服务,我们不必关心服务内部具体是如何处理问题的,我们只需要关注服务给出的接口。
|
||||
|
||||
并不是所有的模块都适合做成服务,一个服务首先最重要的是独立性,这个服务必须可以独立的完成指定的工作。复杂的服务可能依赖于一个或者多个更基础的服务,但是服务通常不应当依赖于任何具体的业务代码,服务必须具有高度的抽象性。关系型数据库服务就具有高度的抽象性,事实上只要我们撰写标准的SQL,不论后面是MySQL、SQL Server还是Oracle,他们都会呈现出几乎完全相同的行为。
|
||||
|
||||
一个更为简单的服务像是缓存服务,我们把一坨数据放进去,在一段时间内可以快速的获取这坨数据,在一段时间后数据就会消失。
|
||||
|
||||
当你的代码需要一个高度抽象高度标准化的功能,而这个功能又不能简单的实现,或者这个功能需要很多资源的配合,例如缓存服务需要内存资源,而数据库服务通常需要磁盘资源,身份验证服务通常需要数据库服务支持。这个时候就可以考虑将这个功能模块做成一个服务。
|
||||
|
||||
服务作为基础的部件,我们通常会要求它能够应付各种各样的情况,一个优质的服务通常会有非常高的可用性,因为我们的系统可能会依赖于各种各样的服务,而整个系统的可用性将不可能比其中任何一个服务的可用性更高。
|
||||
|
||||
所以服务的特征:抽象、独立、稳定。
|
||||
|
||||
评论中提到Java项目中的Service通常是指Business Service,这里也简单说说。
|
||||
|
||||
很多时候,我们发现服务的特征对于我们开发一个大型项目的时候很有帮助。就拿独立性来说,关系型数据库服务如SQL Server可以独立发售,独立安装和部署。它可以自行测试自己的接口,如果都达到了预期的效果,并且能够应付各种情况,这个服务就可以作为一个产品独立的出售给我们安装。这意味着关系型数据库服务并不需要配合我们的业务系统一起进行测试和调试,或者作出什么变更。
|
||||
|
||||
在完成一个大型的业务系统时,我们发现一些子模块或者子系统也可以像服务一样独立的部署和测试。例如会员系统、支付系统、订单系统等等,他们的业务逻辑可能非常复杂,但是逻辑相对独立,并且高度内聚。如果我们将这些系统分别独立的测试和部署,就可以大大的降低我们的测试、部署和运维的成本。
|
||||
这些可以独自完成某一方面业务功能,高度内聚,可以独立部署测试的模块,我们可以称之为Business Service,业务服务。它同样具有服务的特征,抽象、独立和稳定。一个会员系统内部的逻辑可能非常复杂(积分规则,分级规则,风险控制,行为数据),但是在其外部,会员的概念可以非常简单
|
||||
|
||||
## 4.Util
|
||||
Util,Util通常来说是我们找不到合适的名字的时候的选择,Util就是工具,在做项目的时候我们总会遇到一些奇奇怪怪的小功能或者重复的代码需要提取。像是URL编码或者解码(当然这个类库通常会提供,不过就以 .NET Framework 为例,提供这个方法的类型名称叫做HttpUtility),或是自创的加密签名算法等等。
|
||||
|
||||
## 5.Model
|
||||
Model,模型,通常来讲,我们会把模型和另一个东西放在一起来说:View,视图。
|
||||
|
||||
模型通常认为是视图的内核,何谓之视图?我们正在与之交互的知乎网站的界面就是视图,而模型是指他的内核:数据。
|
||||
|
||||
知乎的数据是问题和答案,问题分为标题和描述,答案有内容和作者以及各种状态。知乎有很多个UI,例如移动页面,普通PC页面,手机APP,以及改版前的旧界面,这些被称作不同的视图。而所有这些形态迥异的视图,其内核都是一样的,这个内核我们就称之为模型(Model)。
|
||||
|
||||
将Model和View的概念拆分开来,有助于我们关注不同的方面,也可以更有效的分工。有些工程师更关注于内核也就是模型,通常来说,他们被称之为后端工程师。有些工程师更关注于用户界面的交互和展示,通常来说,他们被称之为前端工程师。
|
||||
|
||||
原文链接:
|
||||
https://www.zhihu.com/question/58410621/answer/157049250
|
|
@ -0,0 +1,44 @@
|
|||
系统中的密码等用户信息,肯定不能用明文来存储。如果有发生信息泄露等问题,用明文存储的密码就太危险了。所以一般我们都用md5等方式来对密码进行加密处理。
|
||||
以下代码就可以用来生成字符串的md5加密。
|
||||
|
||||
```
|
||||
public class Md5UtilDemo {
|
||||
|
||||
public static String md5(String plainText) {
|
||||
String encryptText = null;
|
||||
try {
|
||||
//拿到一个md5转换器
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(plainText.getBytes("UTF8"));
|
||||
byte s[] = md.digest();
|
||||
String result = "";
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
result += Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6);
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return encryptText;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String inputStr = "abc";
|
||||
String result = md5(inputStr);
|
||||
System.out.println(result);
|
||||
System.out.println(result.length());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
代码run起来的结果:
|
||||
|
||||
```
|
||||
900150983cd24fb0d6963f7d28e17f72
|
||||
32
|
||||
```
|
||||
|
||||
其中,`Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6)`的作用 是显示一个byte型的单字节十六进制(两位十六进制表示)的编码。
|
||||
|
||||
byteVar & 0x000000FF的作用是,如果byteVar 是负数,则会清除前面24个零,正的byte整型不受影响。(...) | 0xFFFFFF00的作用是,如果byteVar 是正数,则置前24位为一,这样toHexString输出一个小于等于15的byte整型的十六进制时,倒数第二位为零且不会被丢弃,这样可以通过substring方法进行截取最后两位即可。
|
|
@ -0,0 +1,80 @@
|
|||
在日常开发中通常我们会存储配置参数信息到属性文件,这些属性文件最常见的就是键值对文件。对于配置文件来说,最常见的操作无非就这两种:读与写。以下针对这两种场景我们来做一个详细的示例。
|
||||
|
||||
## 1.读取properties文件中的键值对
|
||||
假设我们在与src平级的路径中有一个conf文件夹,里面有个province.properties的属性文件,文件里的内容格式如下:
|
||||
|
||||
```
|
||||
北京市={"citycode":"010","adcode":"110100","name":"北京市","center":"116.405285,39.904989","level":"city","districts":[{"citycode":"010","adcode":"110101","name":"东城区","center":"116.418757,39.917544","level":"district","districts":[]},{"citycode":"010","adcode":"110102","name":"西城区","center":"116.366794,39.915309","level":"district","districts":[]},{"citycode":"010","adcode":"110105","name":"朝阳区","center":"116.486409,39.921489","level":"district","districts":[]},{"citycode":"010","adcode":"110106","name":"丰台区","center":"116.286968,39.863642","level":"district","districts":[]},{"citycode":"010","adcode":"110107","name":"石景山区","center":"116.195445,39.914601","level":"district","districts":[]},{"citycode":"010","adcode":"110108","name":"海淀区","center":"116.310316,39.956074","level":"district","districts":[]},{"citycode":"010","adcode":"110109","name":"门头沟区","center":"116.105381,39.937183","level":"district","districts":[]},{"citycode":"010","adcode":"110111","name":"房山区","center":"116.139157,39.735535","level":"district","districts":[]},{"citycode":"010","adcode":"110112","name":"通州区","center":"116.658603,39.902486","level":"district","districts":[]},{"citycode":"010","adcode":"110113","name":"顺义区","center":"116.653525,40.128936","level":"district","districts":[]},{"citycode":"010","adcode":"110114","name":"昌平区","center":"116.235906,40.218085","level":"district","districts":[]},{"citycode":"010","adcode":"110115","name":"大兴区","center":"116.338033,39.728908","level":"district","districts":[]},{"citycode":"010","adcode":"110116","name":"怀柔区","center":"116.637122,40.324272","level":"district","districts":[]},{"citycode":"010","adcode":"110117","name":"平谷区","center":"117.112335,40.144783","level":"district","districts":[]},{"citycode":"010","adcode":"110118","name":"密云区","center":"116.843352,40.377362","level":"district","districts":[]},{"citycode":"010","adcode":"110119","name":"延庆区","center":"115.985006,40.465325","level":"district","districts":[]}]}
|
||||
|
||||
```
|
||||
|
||||
现在我们想把里层的name解析出来,代码如下
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
Properties prop = new Properties();
|
||||
String str = Demo1.class.getClassLoader().getResource("").getPath();
|
||||
String input = str + "/conf/province.properties";
|
||||
InputStream is = new BufferedInputStream(new FileInputStream(
|
||||
prop.load(new InputStreamReader(is,"UTF-8"));
|
||||
// 如下写法也可以:
|
||||
// InputStream is = XXXClass.class.getClassLoader().getResourceAsStream("province.properties");
|
||||
Set<Object> keySet = prop.keySet();
|
||||
for(String key: keySet.toArray(new String[keySet.size()])) {
|
||||
String line = prop.getProperty(key);
|
||||
JSONObject json = JSONObject.fromObject(line);
|
||||
JSONArray jsonArray = json.getJSONArray("districts");
|
||||
for(int i=0; i < jsonArray.size(); i++) {
|
||||
JSONObject jsonObject = jsonArray.getJSONObject(i);
|
||||
String name = jsonObject.getString("name");
|
||||
System.out.println(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
api层面的使用方式很简单,使用Properties对象的getProperty方法就可以找到对应属性的值。如果没有找到相应属性,则返回null。
|
||||
|
||||
## 2.向内存或属性文件中写入键值对
|
||||
上面的例子中我们演示了如何读取键值对,接下来我们再演示一下如何写入键值对。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test() {
|
||||
try {
|
||||
OutputStream os = new FileOutputStream("test.properties");
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty("name", "leilei");
|
||||
prop.setProperty("age", "18");
|
||||
prop.setProperty("interest", "algorithm"); //将键值对写入内存
|
||||
|
||||
//通过keySet遍历
|
||||
Set<Object> keys = prop.keySet();
|
||||
for (String key : keys.toArray(new String[0])) {
|
||||
System.out.println(key + "\t" + prop.get(key));
|
||||
}
|
||||
|
||||
//通过entrySet遍历
|
||||
Set<Map.Entry<Object,Object>> entrys = prop.entrySet();
|
||||
for(Map.Entry<Object,Object> entry: entrys) {
|
||||
System.out.println(entry.getKey().toString() + "\t" + entry.getValue().toString());
|
||||
}
|
||||
|
||||
//通过propertyNames遍历
|
||||
Enumeration<?> e = prop.propertyNames();
|
||||
while(e.hasMoreElements()) {
|
||||
String key = (String) e.nextElement();
|
||||
String value = prop.getProperty(key);
|
||||
System.out.println(key + "\t" + value);
|
||||
}
|
||||
|
||||
prop.store(os, "test properties " + new Date().toString()); //写入文件
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Properties类可以通过setProperty方法将键值对保存到内存中,此时可以通过getProperty方法读取。如果我们想将键值对持久化到文件中,可以使用store()方法将键值对写入到属性文件中。
|
Loading…
Reference in New Issue