add java
parent
5768a06296
commit
118623ff4d
|
@ -0,0 +1,23 @@
|
|||
java的一个设计理念是,与泛型相关的异常最好是在编译期间就被发现,因此设计了extends与super这两种方式。
|
||||
具体来说,List<? extends T>表示该集合中存在的都是类型T的子类,包括T自己。
|
||||
而List<? super T>表示该集合中存的都是类型T的父类,包括T自己。
|
||||
|
||||
List<? extends T>如果去添加元素的时候,因为list中存放的其实是T的一种子类,如果我们去添加元素,其实不知道到底应该添加T的哪个子类,这个时候桥接方法在进行强转的时候会出错。但是如果是从集合中将元素取出来,我们可以知道取出来的元素肯定是T类型。所以? extends T这种方式可以取元素而不能添加,这个叫get原则。
|
||||
|
||||
List<? super T>因为存的都是类型T的父类,所以如果去添加T类或者T类子类的元素,肯定是可以的。但是如果将元素取出来,则不知道到底是什么类型,所以? super T可以添加元素但是没法取出来,这个叫put原则。
|
||||
|
||||
```
|
||||
public static void test() {
|
||||
List<? extends Number> l1 = new ArrayList<>();
|
||||
// l1.add(1); 会报错
|
||||
|
||||
List<? super Number> l2 = new ArrayList<>();
|
||||
// Number n = l2.get(1); 会报错
|
||||
l2.add(1);
|
||||
l2.add(2);
|
||||
l2.add(0.11);
|
||||
for(Object n: l2) {
|
||||
System.out.println(n.toString());
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,247 @@
|
|||
## 1.两个接口的原型
|
||||
|
||||
Java中,Comparable与Comparator接口都是用来做比较的。那么这两个接口在实际使用中到底有什么不同呢?下面我们来结合实例分析一下。
|
||||
|
||||
先看看两个接口在JDK中的原型。
|
||||
|
||||
```
|
||||
package java.lang;
|
||||
import java.util.*;
|
||||
|
||||
public interface Comparable<T> {
|
||||
public int compareTo(T o);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
package java.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.function.ToLongFunction;
|
||||
import java.util.function.ToDoubleFunction;
|
||||
import java.util.Comparators;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Comparator<T> {
|
||||
int compare(T o1, T o2);
|
||||
boolean equals(Object obj);
|
||||
}
|
||||
```
|
||||
|
||||
## 2.Comparable的用法
|
||||
一般来说,Comparable是为了对某个类的集合进行排序,所以此时一般都是这个需要排序的类本身去实现Comparable接口。换句话说,如果某个类实现了Comparable接口,那么这个类的数组或者说List就可以进行排序了。
|
||||
|
||||
举个简单的例子:
|
||||
|
||||
```
|
||||
public class Employee implements Comparable<Employee> {
|
||||
|
||||
private String name;
|
||||
private int salary;
|
||||
|
||||
public Employee(String name, int salary) {
|
||||
this.name = name;
|
||||
this.salary = salary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Employee other) {
|
||||
return this.salary - other.salary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name is: " + name + ", salary is: " + salary;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getSalary() {
|
||||
return salary;
|
||||
}
|
||||
|
||||
public void setSalary(int salary) {
|
||||
this.salary = salary;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在客户端中实现Employee集合排序:
|
||||
|
||||
```
|
||||
public class CompareTest {
|
||||
|
||||
public static List<Employee> genList() {
|
||||
Employee e1 = new Employee("aaa",100);
|
||||
Employee e2 = new Employee("bbb",150);
|
||||
Employee e3 = new Employee("ccc", 80);
|
||||
List<Employee> list = new ArrayList();
|
||||
|
||||
list.add(e1);
|
||||
list.add(e2);
|
||||
list.add(e3);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void t1() {
|
||||
List<Employee> list = genList();
|
||||
//Collections.sort(list); 两种方式都可以,此种方式源码中就是调用的list.sort(null)
|
||||
list.sort(null);
|
||||
System.out.println(list);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
t1();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
将客户端的代码run起来,最后输出的结果为:
|
||||
|
||||
```
|
||||
[name is: ccc, salary is: 80, name is: aaa, salary is: 100, name is: bbb, salary is: 150]
|
||||
```
|
||||
|
||||
因为Employee实现了Comparable接口,所以能直接对Employee数组进行排序。
|
||||
|
||||
## 3.Comparator接口用法
|
||||
很多时候我们无法对类进行修改,或者说此类修改的成本太高,但是又希望对其进行排序。那怎么办?这个时候Comparator接口就排上了用场。
|
||||
比如我们将前面的Employee类稍作修改,不实现Comparable接口,加上final关键字:
|
||||
|
||||
```
|
||||
public final class Employee {
|
||||
|
||||
private String name;
|
||||
private int salary;
|
||||
|
||||
public Employee(String name, int salary) {
|
||||
this.name = name;
|
||||
this.salary = salary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name is: " + name + ", salary is: " + salary;
|
||||
}
|
||||
...此处省略get/set
|
||||
}
|
||||
```
|
||||
|
||||
这个时候我们显然无法修改Employee类了。但是还是需要对其排序,怎么办?
|
||||
|
||||
如果在jdk8之前,使用匿名内部类的方式:
|
||||
|
||||
```
|
||||
public static void test() {
|
||||
List<Employee> list = genList();
|
||||
Collections.sort(list, new Comparator<Employee>() {
|
||||
@Override
|
||||
public int compare(Employee o1, Employee o2) {
|
||||
return o1.getSalary() - o2.getSalary();
|
||||
}
|
||||
});
|
||||
System.out.println(list);
|
||||
}
|
||||
```
|
||||
|
||||
在jdk8之后,可以使用lambda表达式:
|
||||
|
||||
```
|
||||
public static void test() {
|
||||
List<Employee> list = genList();
|
||||
Comparator<Employee> comparator = (Employee e1, Employee e2) -> e1.getSalary() - e2.getSalary();
|
||||
list.sort(comparator);
|
||||
System.out.println(list);
|
||||
}
|
||||
```
|
||||
|
||||
如果将此方法run起来,输出如下:
|
||||
|
||||
```
|
||||
[name is: ccc, salary is: 80, name is: aaa, salary is: 100, name is: bbb, salary is: 150]
|
||||
```
|
||||
|
||||
同学们可能会注意到,Comparable接口中只有一个compareTo方法要实现,而Comparator有两个方法,但是我们只实现了一个方法,那么另外一个方法呢?
|
||||
|
||||
其实很简单,因为另外一个方法是equals方法。所有的类都继承了Object类,而Object类中实现了equals方法,所以我们这里不实现equals方法也无所谓!
|
||||
|
||||
|
||||
## 4.Comparator中的各种实现方式比较
|
||||
|
||||
Comparator中的compare方法实现方式还是比较多的。下面我们来一一说明。
|
||||
|
||||
### 4.1 传统的匿名内部类
|
||||
JDK8之前,一般是采用匿名内部类的方式实现:
|
||||
|
||||
```
|
||||
Collections.sort(list, new Comparator<Employee>() {
|
||||
@Override
|
||||
public int compare(Employee o1, Employee o2) {
|
||||
return o1.getSalary() - o2.getSalary();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4.2 lambda表达式
|
||||
JDK8之后,可以使用lambda表达式:
|
||||
|
||||
```
|
||||
list.sort((Employee e1, Employee e2) -> e1.getSalary() - e2.getSalary());
|
||||
```
|
||||
|
||||
### 4.3 精简版的lambda表达式
|
||||
我们通过不指定类型定义来进一步简化表达式,因为编译器自己可以进行类型判断
|
||||
|
||||
```
|
||||
list.sort((e1, e2) -> e1.getSalary() - e2.getSalary());
|
||||
```
|
||||
|
||||
### 4.4 使用Comparator.comparing的方式
|
||||
我们使用上述lambda表达式的时候,IDE会提示我们:can be replaced with comparator.comparing Int
|
||||
|
||||
```
|
||||
list.sort(Comparator.comparing(employee -> employee.getSalary()));
|
||||
```
|
||||
|
||||
### 4.5 使用静态方法的引用
|
||||
java中的双冒号就是方法引用。::是JDK8里引入lambda后的一种用法,表示引用,比如静态方法的引用String::valueOf,比如构造器的引用,ArrayList::new。
|
||||
|
||||
|
||||
```
|
||||
list.sort(Comparator.comparing(Employee::getSalary));
|
||||
```
|
||||
|
||||
### 4.6 排序反转
|
||||
很多时候,想对排序进行反转,或者说逆序:
|
||||
|
||||
```
|
||||
list.sort(Comparator.comparing(Employee::getSalary).reversed());
|
||||
```
|
||||
|
||||
### 4.7 许多条件组合排序
|
||||
|
||||
```
|
||||
list.sort((e1, e2) -> {
|
||||
if(e1.getSalary() != e2.getSalary()) {
|
||||
return e1.getSalary() - e2.getSalary();
|
||||
} else {
|
||||
return e1.getName().compareTo(e2.getName());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4.8 从JDK 8开始,我们现在可以把多个Comparator链在一起(chain together)去建造更复杂的比较逻辑
|
||||
|
||||
```
|
||||
list.sort(Comparator.comparing(Employee::getSalary).thenComparing(Employee::getName));
|
||||
```
|
|
@ -0,0 +1,99 @@
|
|||
## 1.HashMap的基础结构
|
||||
在1.7中,HashMap 底层是基于 数组 + 链表的结构组成。在1.8中,如果链表的长度大于一定的值,链表会转成红黑树。
|
||||
|
||||
## 2.HashMap的参数(1.8)
|
||||
|
||||
```
|
||||
/**
|
||||
* The default initial capacity - MUST be a power of two.
|
||||
*/
|
||||
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
|
||||
|
||||
/**
|
||||
* The maximum capacity, used if a higher value is implicitly specified
|
||||
* by either of the constructors with arguments.
|
||||
* MUST be a power of two <= 1<<30.
|
||||
*/
|
||||
static final int MAXIMUM_CAPACITY = 1 << 30;
|
||||
|
||||
/**
|
||||
* The load factor used when none specified in constructor.
|
||||
*/
|
||||
static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
/**
|
||||
* The bin count threshold for using a tree rather than list for a
|
||||
* bin. Bins are converted to trees when adding an element to a
|
||||
* bin with at least this many nodes. The value must be greater
|
||||
* than 2 and should be at least 8 to mesh with assumptions in
|
||||
* tree removal about conversion back to plain bins upon
|
||||
* shrinkage.
|
||||
*/
|
||||
static final int TREEIFY_THRESHOLD = 8;
|
||||
|
||||
/**
|
||||
* The bin count threshold for untreeifying a (split) bin during a
|
||||
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
|
||||
* most 6 to mesh with shrinkage detection under removal.
|
||||
*/
|
||||
static final int UNTREEIFY_THRESHOLD = 6;
|
||||
|
||||
/**
|
||||
* The smallest table capacity for which bins may be treeified.
|
||||
* (Otherwise the table is resized if too many nodes in a bin.)
|
||||
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
|
||||
* between resizing and treeification thresholds.
|
||||
*/
|
||||
static final int MIN_TREEIFY_CAPACITY = 64;
|
||||
...
|
||||
|
||||
/**
|
||||
* The number of key-value mappings contained in this map.
|
||||
*/
|
||||
transient int size;
|
||||
...
|
||||
/**
|
||||
* The next size value at which to resize (capacity * load factor).
|
||||
*
|
||||
* @serial
|
||||
*/
|
||||
// (The javadoc description is true upon serialization.
|
||||
// Additionally, if the table array has not been allocated, this
|
||||
// field holds the initial array capacity, or zero signifying
|
||||
// DEFAULT_INITIAL_CAPACITY.)
|
||||
int threshold;
|
||||
|
||||
```
|
||||
|
||||
DEFAULT_INITIAL_CAPACITY:16,数组的初始值,没太多好解释的。
|
||||
MAXIMUM_CAPACITY:最大值,基本上不会达到这个值。
|
||||
DEFAULT_LOAD_FACTOR:size / capacity(DEFAULT_INITIAL_CAPACITY)
|
||||
threshold: capacity * load factor,超过这个阈值会rehash。
|
||||
|
||||
//一个桶的树化阈值
|
||||
//当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点
|
||||
//这个值必须为 8,要不然频繁转换效率也不高
|
||||
static final int TREEIFY_THRESHOLD = 8;
|
||||
|
||||
//一个树的链表还原阈值
|
||||
//当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构
|
||||
//这个值应该比上面那个小,至少为 6,避免频繁转换
|
||||
static final int UNTREEIFY_THRESHOLD = 6;
|
||||
|
||||
//哈希表的最小树形化容量
|
||||
//当哈希表中的容量大于这个值时,表中的桶才能进行树形化
|
||||
//否则桶内元素太多时会扩容,而不是树形化
|
||||
//为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
|
||||
static final int MIN_TREEIFY_CAPACITY = 64;
|
||||
|
||||
## 3.HashMap中DEFAULT_LOAD_FACTOR为什么是0.75
|
||||
如果该值设为0.5,会在hashmap容量达到一半时候就扩容。比如从16扩大32,从32到64,64到128,浪费的空间会越来越大。
|
||||
而如果该值设置为1,则每次空间使用完毕才会扩容,put时候操作耗时会增加。
|
||||
所以0.75是时间与空间的一个平衡。
|
||||
|
||||
## 4.put时候操作
|
||||
1.7插入元素到单链表中采用头插入法,1.8采用的是尾插入法
|
||||
|
||||
## 5.HashMap死循环
|
||||
HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构。
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
## 1.异常分类
|
||||
异常Exception是Java中非常常用的功能,它可以简化代码,并且增强代码的安全性。尤其是在各种服务相关的代码中,可能正常业务逻辑的代码量很少,大部分都是各种try catch处理各种异常的代码,因为实际中异常情况很多,为了保证服务的健壮与稳定性,要尽可能考虑与处理掉各种异常情况。所以在java中遇到大段大段的try catch也就不足为奇。
|
||||
|
||||
![在这里插入图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/exception/1.jpeg)
|
||||
(图片来自网络)
|
||||
|
||||
从上面的图可以看出来,
|
||||
1.Throwable是所有异常的根,java.lang.Throwable
|
||||
2.Throwable包括Error与Exception两个子类。
|
||||
3.Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。对于这类错误,Java编译器不去检查他们,编译器也没法提前发现。对于这类错误导致的应用程序中断,仅仅靠程序本身是无法恢复与预防的。所以对于Error,一般是程序直接终止停止运行。
|
||||
4.Exception类为程序可以处理的异常。遇到这种类型的异常,一般在代码中会去做相关处理,并且让程序恢复运行,而不是直接让程序终止运行。
|
||||
|
||||
## 2.Runtime Exception 与 Checked Exception
|
||||
Runtime Exception一般表示虚拟机层面操作中可能遇到的异常,是一种常见运行时错误。运行时异常在代码中不一定要求捕获或者抛出。
|
||||
常见的RuntimeException
|
||||
1.NullPointerException NullPointerException是开发中最常见的UncheckedException,尤其实在代码调试阶段。如果在一个空指针上引用方法或变量或对象等,编译器编译的时候不会报错,但是运行期会抛出NullPointerException。
|
||||
2.ArithmeticException 算术运算异常 最常见的为除数为0
|
||||
3.IllegalArgumentException 传递非法参数异常
|
||||
4. IndexOutOfBoundsException 下标越界异常 这个我们在处理各种数组,集合的时候也经常遇到。
|
||||
![在这里插入图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/exception/2.png)
|
||||
|
||||
Checked exceptions与runtime exception的目的不尽相同。Checked exception一般用来指示一种调用方能够直接处理的异常情况。而runtime exception一般是虚拟机层面的问题,代表一种调用方本身无法处理或恢复的程序错误。
|
||||
|
||||
checked exceptions意味着不在程序的即时控制内的错误场景。它们通常与外部资源/网络资源交互,例如数据库问题、网络连接错误、丢失文件等。程序中经常要处理各种资源文件,所以像FileNotFoundException这种异常就很常见。FileNotFoundException的继承关系如下图。
|
||||
|
||||
![在这里插入图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/exception/3.png)
|
||||
|
||||
## 3.异常处理的基本方法
|
||||
Java的异常处理本质上是抛出异常和捕获异常。
|
||||
抛出异常:要理解抛出异常,首先要明白什么是异常情形(exception condition),它是指阻止当前方法或作用域继续执行的问题。其次把异常情形和普通问题相区分,普通问题是指在当前环境下能得到足够的信息,总能处理这个错误。对于异常情形,已经无法继续下去了,因为在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境中跳出,并把问题提交给上一级环境,这就是抛出异常时所发生的事情。抛出异常后,会有几件事随之发生。首先,是像创建普通的java对象一样将使用new在堆上创建一个异常对象;然后,当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序,这个恰当的地方就是异常处理程序或者异常处理器,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
|
||||
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
|
||||
|
||||
对于运行时异常、错误和检查异常,Java技术所要求的异常处理方式有所不同。
|
||||
|
||||
由于运行时异常及其子类的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
|
||||
|
||||
对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
|
||||
|
||||
对于所有的检查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉检查异常时,它必须声明将抛出异常。
|
||||
(此部分内容来自参考文献1)
|
||||
|
||||
## 4.异常处理常见的样例
|
||||
与异常处理相关的关键字有如下几个:try,catch,finally,throw,throws。所有异常的处理都围绕这几个字展开。
|
||||
try: 用于监听,判断try代码块中的内容是否有异常。如果发生异常,将会被跑出来。
|
||||
catch: 捕获try代码块中的相关异常。
|
||||
finally: finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。比如关闭数据库连接、断开网络连接和关闭磁盘文件等。
|
||||
throw: 用来抛出异常。
|
||||
throws: 如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明,用在方法签名中。
|
||||
|
||||
### 4.1 try-catch方式捕获异常
|
||||
这种是最常见的处理异常的方式。一般的用法如下:
|
||||
|
||||
```
|
||||
try{
|
||||
//do something and might generate some exceptions
|
||||
}catch(Exception e1){
|
||||
//handling exception1
|
||||
}catch(Exception e2){
|
||||
//handling exception2
|
||||
}
|
||||
```
|
||||
|
||||
如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配。
|
||||
如果有多重catch,整体的原则为:先catch住子类,再catch父类,层层递进。
|
||||
|
||||
举个例子:
|
||||
|
||||
```
|
||||
try{
|
||||
//code open a file
|
||||
}catch(FileNotFoundException e1){
|
||||
//handling e1
|
||||
}catch(IOException e2){
|
||||
//handling e2
|
||||
}catch(Exception e3){
|
||||
//handling e3
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 try-catch-finally方式捕获异常
|
||||
这种方式也很常见。与上面唯一的区别在于多了一个finally部分用来处理一些售后工作。
|
||||
|
||||
```
|
||||
try{
|
||||
//code open a file
|
||||
}catch(FileNotFoundException e1){
|
||||
//handling e1
|
||||
}catch(IOException e2){
|
||||
//handling e2
|
||||
}catch(Exception e3){
|
||||
//handling e3
|
||||
}finally(){
|
||||
//close the file
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 方法中throw出异常
|
||||
上面的方式,我们都是去捕获系统抛出的异常,当然我们也可以抛出来异常。在实际项目中,我们经常自己定义各种异常,方便快速定位与查找系统异常。
|
||||
自己抛出异常也非常简单
|
||||
|
||||
```
|
||||
throw ThrowableObject
|
||||
```
|
||||
只需要抛出一个Throwable的对象即可。比如我们经常用类似的方法这么做:
|
||||
|
||||
```
|
||||
public class TestThrow extends Exception {
|
||||
|
||||
public static class SomeException extends Exception {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
throw new SomeException();
|
||||
} catch (SomeException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
先定义一个名为SomeException的类,该类继承自Exception。然后在逻辑中先抛出这个异常再catch住。
|
||||
注意throw是在函数内。
|
||||
|
||||
### 4.4 throws
|
||||
如果一个方法可以导致一个异常,但是在这个方法内我们不想处理,想交给他的调用者去处理,这个时候可以采用throws的方式。要做到这点,我们可以在方法声明中包含一个throws子句。一个throws子句列举了一个方法可能引发的所有异常类型,可以包括多个异常。
|
||||
|
||||
例如我们经常写的mapreduce程序里,mapper阶段的map方法就throws出来有两个异常:
|
||||
|
||||
```
|
||||
protected void map(KEYIN key, VALUEIN value,
|
||||
Context context) throws IOException, InterruptedException {
|
||||
context.write((KEYOUT) key, (VALUEOUT) value);
|
||||
}
|
||||
```
|
||||
|
||||
所以我们每次重写map方法的时候,也会throws出这两个异常。
|
||||
注意throws是在方法签名中,而不是在方法内部!
|
||||
|
||||
|
||||
## 5.throw与throws的区别
|
||||
throw语句在方法体内,标识抛出异常,有方法体内的语句来处理。一旦执行到throw语句,说明肯定要抛出异常,程序执行完throw语句之后立即停止;throw后面的任何语句不被执行,最邻近的try块用来检查它是否含有一个与异常类型匹配的catch语句。如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
|
||||
throws语句在方法签名中,主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。只是说该方法有可能要抛出某些异常。
|
||||
|
||||
### 6.finally中不要改变返回值
|
||||
finally子句是可选项,可以有也可以没有。但是每个try语句至少需要一个catch或者finally子句。
|
||||
如果try里面有个return语句,try 后的 finally{} 里的 code 会在方法返回调用者前被执行。
|
||||
什么意思呢?总结起来一句话:在finally中改变返回值的做法是不好的。因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰。
|
||||
看两个例子:
|
||||
|
||||
```
|
||||
public static int test() {
|
||||
int result = 0;
|
||||
try {
|
||||
result = 1;
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
result = 2;
|
||||
return result;
|
||||
} finally {
|
||||
result = 3;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面这段代码,方法的返回值是1。
|
||||
|
||||
```
|
||||
public class TestFinally extends Exception {
|
||||
public static class Person {
|
||||
public String name;
|
||||
public Person(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static Person test() {
|
||||
Person person = new Person("lili");
|
||||
try {
|
||||
person.name = "mm";
|
||||
return person;
|
||||
} catch (Exception ex) {
|
||||
return person;
|
||||
} finally {
|
||||
person.name = "yy";
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Person person = test();
|
||||
System.out.println(person.name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以上代码输出为yy!
|
||||
|
||||
如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
|
||||
|
||||
1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
|
||||
|
||||
2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
|
||||
|
||||
不管怎么说,在finally中返回或者修改返回值都不是一件好事情,墙裂建议大家不这么干。
|
||||
|
||||
## 参考文献:
|
||||
1.https://www.cnblogs.com/Qian123/p/5715402.html#_label2
|
||||
2.https://blog.csdn.net/Next_Second/article/details/73090994
|
|
@ -0,0 +1,31 @@
|
|||
## 1.java.nio中的Buffer
|
||||
java.nio(NEW IO)是JDK 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, NIO支持面向缓冲区的、基于通道的IO操作。 NIO将以更加高效的方式进行文件的读写操作。
|
||||
|
||||
而缓冲区Buffer是一个容器对象,底层的存储结构为一个数组。在NIO中,所有的数据都是缓冲区来处理的。而使用缓冲区的好处显而易见:第一可以减少实际物理的读写次数,第二缓冲区创建初始就被分配了内存,这个内存空间一直在被重用,可以减少动态分配和回收内存的次数。
|
||||
|
||||
在nio库中,Buffer是一个抽象类,具体的实现类可以参考下图
|
||||
|
||||
![在这里插入图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/bytelong/1.png)
|
||||
|
||||
## 2.long转byte数组
|
||||
|
||||
```
|
||||
public static byte[] toByteArray(long value) {
|
||||
return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array();
|
||||
}
|
||||
```
|
||||
|
||||
## 3.byte数组转long
|
||||
|
||||
```
|
||||
public static long byteArrayToLong(byte[] bytes) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(8);
|
||||
buffer.put(bytes, 0, bytes.length);
|
||||
buffer.flip();
|
||||
return buffer.getLong();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 4.allocate
|
||||
上面的allocate方法,作用是从堆空间中分配一个容量大小为capacity的byte数组作为缓冲区的byte数据存储器,所有的数据操作,都是在这个byte数组中完成的。
|
|
@ -0,0 +1,181 @@
|
|||
原文链接
|
||||
https://www.jianshu.com/p/4aea469f8e47
|
||||
|
||||
## 一. 主存与工作内存
|
||||
说 volatile 之前,先来聊聊 Java 的内存模型。
|
||||
|
||||
在 Java 内存模型中,规定了所有的变量都是存储在主内存当中,而每个线程都有属于自己的工作内存。线程的工作内存保存了被该内存使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
|
||||
|
||||
对于单线程的程序,这样的规定没有任何影响;但是对于多线程的程序,便可能导致,某个线程已经改变了主内存中的变量,而另一个线程还在使用其工作内存中的变量,因此造成了数据的不一致。
|
||||
|
||||
![在这里插入图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/volatile/1.png)
|
||||
|
||||
## 二. 可见性
|
||||
volatile 可以保证数据的可见性,前面说到对于多线程的程序可能会造成数据不一致,但是当一个变量加上 volatile 之后,便可以保证,其他线程读取到的该变量都是最新值。
|
||||
|
||||
这是因为每当对该变量进行写操作时,都会使得其他线程工作变量中的该变量的拷贝失效,而迫使线程们都重新去主内存读取
|
||||
|
||||
我们来看看实例:
|
||||
|
||||
```
|
||||
public class TestThread extends Thread {
|
||||
private volatile boolean isRunning = true;
|
||||
|
||||
public void setRunning(boolean running) {
|
||||
isRunning = running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int i = 1;
|
||||
while (isRunning) {
|
||||
i++;
|
||||
}
|
||||
System.out.println(i);
|
||||
}
|
||||
}
|
||||
|
||||
public class Test {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
TestThread thread = new TestThread ();
|
||||
thread.start();
|
||||
Thread.sleep(3000);
|
||||
thread.setRunning(false);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
当 isRunning 变量没有添加 volatile 变量时,该程序会发生死循环,因为setRunning(false)并没有影响到 thread 所在线程的工作内存(这时该线程看到的值仍然是 true)
|
||||
|
||||
当我们为变量添加上 volatile 之后,setRunning(false)执行完毕,thread 所在线程的工作内存的变量拷贝便就此作废,必须去主内存获取最新的值,死循环也因此不会再发生了
|
||||
|
||||
值得注意的是,当我们在循环中添加了打印语句,或者 sleep 方法等,这时无论有没有 volatile,都会停止循环,如:
|
||||
|
||||
```
|
||||
while (isRunning) {
|
||||
i++;
|
||||
System.out.println(i);
|
||||
}
|
||||
```
|
||||
|
||||
这是因为,JVM 会尽力保证内存的可见性,原本的代码中,程序一直处于死循环,这时 JVM 没有办法强制要求 CPU 分出时间去保证可见性;但是当加上打印语句之后,CPU 便会分出时间去处理这件事情,并保证了可见性;但是,与之不同的是,volatile 是强制保证可见性的。
|
||||
|
||||
|
||||
## 三. 原子性
|
||||
volatile 没有办法保证操作的原子性的
|
||||
|
||||
直接上代码:
|
||||
|
||||
```
|
||||
public class AtomicTest {
|
||||
private static volatile int race = 0;
|
||||
|
||||
private static void increase() {
|
||||
race++;
|
||||
}
|
||||
|
||||
private static final int THREADS_COUNT = 20;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Thread[] threads = new Thread[THREADS_COUNT];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
threads[i] = new Thread(() -> {
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
increase();
|
||||
}
|
||||
});
|
||||
threads[i].start();
|
||||
}
|
||||
//等待所有累加线程都结束
|
||||
while (Thread.activeCount() > 1) {
|
||||
Thread.yield();
|
||||
}
|
||||
System.out.println(race);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码摘自《深入理解 Java 虚拟机》12 章,当 race 没有 volatile 关键字的加持时,最终的打印结果经常会小于 10000,而有了 volatile,这段程序变不再出现这种情况。
|
||||
|
||||
假设两个线程 1 和 2,它们俩先后读取了 race 的值(初始值为 0),由于它们都还没有进行写操作,因此两个线程这时看到的值都是 0,因此便使得之后两次自增操作的结果是 1,而不是 2
|
||||
|
||||
|
||||
![在这里插入图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/volatile/2.png)
|
||||
|
||||
刚刚说到 volatile 变量在进行写操作的时候,会让其他线程对应的工作内存中的拷贝失效,使得需要直接去主存中读取变量,而上例中线程 1 在进行写操作之前,线程 2 便已经执行了读操作,因此没办法影响线程 2 的读取,因此也不会更新为最新的数据了
|
||||
|
||||
## 四. 有序性
|
||||
volatile 可以在一定程度上禁止指令重排序
|
||||
|
||||
```
|
||||
//x、y为非volatile变量
|
||||
//flag为volatile变量
|
||||
|
||||
x = 2; //语句1
|
||||
y = 0; //语句2
|
||||
flag = true; //语句3
|
||||
x = 4; //语句4
|
||||
y = -1; //语句5
|
||||
```
|
||||
|
||||
flag 变量添加上 volatile 关键字以后,语句 1,2 不会排在 3 的后面执行,当然 4,5 也不会在 3 的前面执行
|
||||
|
||||
但是 1 和 2, 3 和 4 之间的顺序没办法干预,这也是我们说“一定程度改变”的原因
|
||||
|
||||
上个例子:
|
||||
|
||||
```
|
||||
//线程1:
|
||||
context = loadContext(); //语句1
|
||||
inited = true; //语句2
|
||||
|
||||
//线程2:
|
||||
while(!inited){
|
||||
sleep()
|
||||
}
|
||||
doSomethingwithconfig(context); //出错,context 可能还没有初始化
|
||||
```
|
||||
|
||||
面对这样的例子的时候,如果 inited 是非 volatile 变量,那么因为重排序的关系,有可能出错;但是加上 volatile 后便不用担心了
|
||||
|
||||
|
||||
## 五. 使用场景
|
||||
1. 状态变量:
|
||||
比如上面给出的可见性的代码例子
|
||||
|
||||
```
|
||||
while (isRunning) {
|
||||
i++;
|
||||
}
|
||||
```
|
||||
对于这种用于标记状态的变量,volatile 是非常好用的
|
||||
|
||||
2. 双重检验:
|
||||
最经典的就是单例模式的双重检验实现,如果忘了的刚好复习一下:
|
||||
|
||||
```
|
||||
public class Singleton {
|
||||
private volatile static Singleton singleton;
|
||||
|
||||
private Singleton() {
|
||||
}
|
||||
|
||||
public static Singleton getInstance() {
|
||||
if (singleton == null) {
|
||||
synchronized (Singleton.class) {
|
||||
if (singleton == null) {
|
||||
singleton = new Singleton();
|
||||
}
|
||||
}
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
```
|
||||
|
||||
这里的 volatile,是为了保证 singleton = new Singleton();操作的有序性,因为 singleton = new Singleton(); 并不是原子操作,做了 3 件事
|
||||
|
||||
给 singleton 分配内存
|
||||
调用 Singleton 的构造函数来初始化成员变量
|
||||
将 singleton 对象指向分配的内存空间(执行完这步 singleton 就为非 null 了)
|
||||
但是由于重排序的原因,1-2-3 的顺序可能变成 1-3-2,如果是后者,在 singleton 变成非 null 时(即第三步),如果第二个线程开始进入第一个判断 if (singleton == null),那么便会直接返回 true,然而事实上 singleton 还没有完成初始化。
|
|
@ -0,0 +1,70 @@
|
|||
## 1.可变参数的定义
|
||||
从JDK1.5之后,java就提供了变长参数(variable arguments,varargs)。我们在定义方法的时候,可以使用不确定个数的参数。对于同一个方法,也可以通过不确定参数个数的方式进行重载。首先来看个最简单的例子:
|
||||
|
||||
```
|
||||
public void printArray(String... args) {
|
||||
for(int i=0; i<args.length; i++) {
|
||||
System.out.print(args[i] + " ");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在main方法里调用此方法,例如:
|
||||
|
||||
```
|
||||
printArray("hello","world");
|
||||
```
|
||||
|
||||
这个时候控制台会打印出`hello world `!以上就是可变参数最简单的应用方式。
|
||||
|
||||
## 2.与固定参数方法的比较
|
||||
如果某一方法被调用的时候,既能与固定参数个数的方法match,也能与被重载的有可变参数的方法match,那么优先调用固定参数个数的方法。
|
||||
|
||||
```
|
||||
public class MultiPrameters {
|
||||
|
||||
public void printArray(String... args) {
|
||||
for(int i=0; i<args.length; i++) {
|
||||
System.out.print(args[i] + " ");
|
||||
}
|
||||
}
|
||||
|
||||
public void printArray(String rawString) {
|
||||
System.out.println("only one string!");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
MultiPrameters mul = new MultiPrameters();
|
||||
mul.printArray("hello");
|
||||
mul.printArray("hello","world");
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
将上面的代码run起来以后,控制台输出如下:
|
||||
|
||||
```
|
||||
only one string!
|
||||
hello world
|
||||
```
|
||||
|
||||
## 3.每个方法最多一个变长参数,并且该参数的位置是方法的的最后
|
||||
|
||||
```
|
||||
public void print(String... args,Int num){}
|
||||
|
||||
public void print(String... args,Int... nums){}
|
||||
```
|
||||
|
||||
以上两种写法,都是错误的!
|
||||
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/args/1.png)
|
||||
|
||||
IDE里会直接提示你:Vararg paramter must be the last in the list!
|
||||
|
||||
## 4.注意不能让调用的方法可以与两个可变参数匹配
|
||||
理解起来也不是很复杂,大家看如下示例代码
|
||||
|
||||
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/args/2.png)
|
||||
|
||||
IDE里直接报错,ambiguous method call both! 很明显,main方法调用的时候不知道调用哪个printArray方法!所以我们在实际编码过程中,避免带有变长参数方法的重载
|
|
@ -0,0 +1,84 @@
|
|||
## 1、DepencyManagement应用场景
|
||||
当我们的项目模块很多的时候,我们使用Maven管理项目非常方便,帮助我们管理构建、文档、报告、依赖、scms、发布、分发的方法。可以方便的编译代码、进行依赖管理、管理二进制库等等。
|
||||
|
||||
由于我们的模块很多,所以我们又抽象了一层,抽出一个itoo-base-parent来管理子项目的公共的依赖。为了项目的正确运行,必须让所有的子项目使用依赖项的统一版本,必须确保应用的各个项目的依赖项和版本一致,才能保证测试的和发布的是相同的结果。
|
||||
|
||||
在我们项目顶层的POM文件中,我们会看到dependencyManagement元素。通过它元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号。
|
||||
|
||||
Itoo-base-parent(pom.xml)
|
||||
|
||||
```
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.persistence</groupId>
|
||||
<artifactId>org.eclipse.persistence.jpa</artifactId>
|
||||
<version>${org.eclipse.persistence.jpa.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax</groupId>
|
||||
<artifactId>javaee-api</artifactId>
|
||||
<version>${javaee-api.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
```
|
||||
|
||||
Itoo-base(pom.xml)
|
||||
|
||||
```
|
||||
<!--继承父类-->
|
||||
<parent>
|
||||
<artifactId>itoo-base-parent</artifactId>
|
||||
<groupId>com.tgb</groupId>
|
||||
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../itoo-base-parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>itoo-base</artifactId>
|
||||
<packaging>ejb</packaging>
|
||||
|
||||
<!--依赖关系-->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax</groupId>
|
||||
<artifactId>javaee-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.persistence</groupId>
|
||||
<artifactId>org.eclipse.persistence.jpa</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
```
|
||||
|
||||
这样做的好处:统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,才能保证测试的和发布的是相同的成果,因此,在顶层pom中定义共同的依赖关系。同时可以避免在每个使用的子项目中都声明一个版本号,这样想升级或者切换到另一个版本时,只需要在父类容器里更新,不需要任何一个子项目的修改;如果某个子项目需要另外一个版本号时,只需要在dependencies中声明一个版本号即可。子类就会使用子类声明的版本号,不继承于父类版本号。
|
||||
|
||||
## 2、Dependencies
|
||||
相对于dependencyManagement,所有生命在dependencies里的依赖都会自动引入,并默认被所有的子项目继承。
|
||||
|
||||
## 3、区别
|
||||
dependencies即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(全部继承)
|
||||
|
||||
dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
|
||||
|
||||
## 4、Maven约定优于配置
|
||||
它提出这一概念,为项目提供合理的默认行为,无需不必要的配置。提供了默认的目录
|
||||
|
||||
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/dependency/1.png)
|
||||
|
||||
对于Maven约定优于配置的理解,一方面对于小型项目基本满足我们的需要基本不需要自己配置东西,使用Maven已经配置好的,快速上手,学习成本降低;另一方面,对于不满足我们需要的还可以自定义设置,体现了灵活性。配置大量减少了,随着项目变的越复杂,这种优势就越明显。
|
||||
|
||||
原文链接:https://blog.csdn.net/liutengteng130/article/details/46991829
|
|
@ -0,0 +1,38 @@
|
|||
java中实例化Class类对象的三种方式:
|
||||
第一种、通过forName();
|
||||
第二种、类.class
|
||||
第三种、对象.getClass()
|
||||
|
||||
测试案例:
|
||||
|
||||
```
|
||||
package com.lfl.demo;
|
||||
|
||||
public class GetClassDemo1 {
|
||||
public static void main(String[] args) {
|
||||
Class<?> c1 = null;// ?是泛型中的通配符
|
||||
Class<?> c2 = null;// ?是泛型中的通配符
|
||||
Class<?> c3 = null;// ?是泛型中的通配符
|
||||
try {
|
||||
c1 = Class.forName("com.lfl.demo.Test");// 此方式在开发中较为常用
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
c2 = Test.class;// 通过类.class实例化
|
||||
c3 = new Test().getClass();// 通过Object类中的方法实例化
|
||||
System.out.println("类名称:" +c1.getName());
|
||||
System.out.println("类名称:" +c2.getName());
|
||||
System.out.println("类名称:" +c3.getName());
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
};
|
||||
```
|
||||
|
||||
Class类是什么?
|
||||
==每个类被加载后,系统会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。
|
||||
==Java程序中的各个java类属于同一个事物,描述这类事物的java类名就是Class。
|
||||
Class类描述了类的名字,类的访问属性,类所属的包名,字段名称的列表,方法名称的列表等等。
|
||||
创建Class类的实例,其实例为类被加载后的字节码。
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
java中,double转string可以用Double.toString(d)的方式。但是,这种方式有隐藏的坑,请大家看仔细了:
|
||||
|
||||
```
|
||||
package hello;
|
||||
|
||||
public class DoubleToString {
|
||||
|
||||
public static void test1(double dou) {
|
||||
String dou_str = Double.toString(dou);
|
||||
if (dou_str.equals("20160101")) {
|
||||
System.out.println("YES!");
|
||||
} else {
|
||||
System.out.println("NO!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
double dou = 20160101;
|
||||
test1(dou);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当运行上述代码以后,控制台华丽丽地输出:
|
||||
|
||||
```
|
||||
NO!
|
||||
```
|
||||
|
||||
我们在第六行后面打印出dou_str:
|
||||
|
||||
```
|
||||
2.0160101E7
|
||||
```
|
||||
|
||||
原来jvm这货将double用科学计数法表示了double,怪不得转成string以后变了样。。。
|
||||
|
||||
将上面代码修改如下:
|
||||
|
||||
```
|
||||
package hello;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
public class DoubleToString {
|
||||
|
||||
public static void test2(double dou) {
|
||||
Double dou_obj = new Double(dou);
|
||||
NumberFormat nf = NumberFormat.getInstance();
|
||||
nf.setGroupingUsed(false);
|
||||
String dou_str = nf.format(dou_obj);
|
||||
System.out.println("dou_str is:" + dou_str);
|
||||
if (dou_str.equals("20160101")) {
|
||||
System.out.println("YES!");
|
||||
} else {
|
||||
System.out.println("NO!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
double dou = 20160101;
|
||||
test2(dou);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
再运行,再输出,这下就OK了:
|
||||
|
||||
```
|
||||
dou_str is:20160101
|
||||
YES!
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
很多时候我们需要输出double数字的字符串形式。但是java默认的double输出方式为科学记数法,显然不符合我们的要求,以下两种方法都能达到我们的目的。
|
||||
|
||||
## 1.使用DecimalFormat类
|
||||
|
||||
```
|
||||
|
||||
public static void t1() {
|
||||
Double num1 = 100000000.0;
|
||||
System.out.println(num1); // 1.0E8
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#,##0.00");
|
||||
System.out.println(decimalFormat.format(num1)); // 100,000,000.00
|
||||
}
|
||||
```
|
||||
|
||||
## 2.使用BigDecimal的toString方法
|
||||
|
||||
```
|
||||
public static void t2() {
|
||||
Double num2 = 100000000.123456;
|
||||
BigDecimal bigDecimal = new BigDecimal(num2);
|
||||
System.out.println(num2); // 11.00000000123456E8
|
||||
String res = bigDecimal.toString();
|
||||
System.out.println(res); // 100000000.12345600128173828125
|
||||
BigDecimal bigDecimal2 = new BigDecimal("100000000.123456");
|
||||
System.out.println(bigDecimal2.toString()); // 100000000.123456
|
||||
}
|
||||
```
|
|
@ -0,0 +1,70 @@
|
|||
实际开发过程中,经常会有十进制的数据跟其他进制数据互相转化的情况。比如十进制与二进制,八进制,十六进制之间的相互转化。为了方便起见,特意将java中不同进制间相互转化的代码整理以备后续使用。
|
||||
|
||||
```
|
||||
package leilei.bit.edu.sort;
|
||||
|
||||
public class NumRadixConvert {
|
||||
|
||||
public static void decimal_to_other() {
|
||||
int a = 10;
|
||||
|
||||
//注意返回的类型都是String
|
||||
String hex = Integer.toHexString(a);
|
||||
String oct = Integer.toOctalString(a);
|
||||
String bin = Integer.toBinaryString(a);
|
||||
|
||||
System.out.println("hex is: " + hex);
|
||||
System.out.println("oct is: " + oct);
|
||||
System.out.println("bin is: " + bin);
|
||||
}
|
||||
|
||||
public static void other_to_decimal() {
|
||||
int hex_to_dec = Integer.valueOf("F",16);
|
||||
int oct_to_dec = Integer.valueOf("12",8);
|
||||
int bin_to_dec = Integer.valueOf("111111",2);
|
||||
|
||||
System.out.println("hex_to_dec is: " + hex_to_dec);
|
||||
System.out.println("oct_to_dec is: " + oct_to_dec);
|
||||
System.out.println("bin_to_dec is: " + bin_to_dec);
|
||||
}
|
||||
|
||||
public static void parse_int() {
|
||||
int parse_hex = Integer.parseInt("F",16);
|
||||
int parse_oct = Integer.parseInt("12",8);
|
||||
int parse_bin = Integer.parseInt("111111",2);
|
||||
|
||||
System.out.println("parse_hex is: " + parse_hex);
|
||||
System.out.println("parse_oct is: " + parse_oct);
|
||||
System.out.println("parse_bin is: " + parse_bin);
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
decimal_to_other();
|
||||
System.out.println();
|
||||
other_to_decimal();
|
||||
System.out.println();
|
||||
parse_int();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
代码运行结果
|
||||
|
||||
```
|
||||
hex is: a
|
||||
oct is: 12
|
||||
bin is: 1010
|
||||
|
||||
hex_to_dec is: 15
|
||||
oct_to_dec is: 10
|
||||
bin_to_dec is: 63
|
||||
|
||||
parse_hex is: 15
|
||||
parse_oct is: 10
|
||||
parse_bin is: 63
|
||||
|
||||
```
|
||||
|
||||
代码本身比较简单,也没太多技术含量,就当个笔记吧。
|
|
@ -0,0 +1,632 @@
|
|||
注:本文是根据网络上的文章受启发,自己再写代码总结而成。因为无法找到原文的原始出处,所以没法给出原文链接。文章也会引用部分网络素材内容,如果原作者看到请与我联系。
|
||||
|
||||
## 0.前言
|
||||
本博主的java水平与专业java开发同学相比水平不可同日而语,所以平时有空的话会多写写java代码。正好在网上看到一篇关于java习惯用法的总结,觉得还不错,于是按照文章里的大致架构,自己重新实现了一把里面的相关代码,相当于自己做个小小的总结。
|
||||
|
||||
## 1.基本方法实现
|
||||
equals()
|
||||
hashcode()
|
||||
compareTo()
|
||||
clone()
|
||||
|
||||
### 1.1 equals()实例与hashCode()实例
|
||||
|
||||
```
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Person {
|
||||
String name;
|
||||
int birthday;
|
||||
byte[] raw;
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
// 原文中的代码有误,需要将!符号后面的表达式扩起来
|
||||
if (!(obj instanceof Person)) {
|
||||
return false;
|
||||
}
|
||||
Person other = (Person) obj;
|
||||
return name.equals(other.name)
|
||||
&& birthday == other.birthday
|
||||
&& Arrays.equals(raw, other.raw);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return name.hashCode() + birthday + Arrays.hashCode(raw);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Person leilei = new Person();
|
||||
leilei.name = "leilei";
|
||||
leilei.birthday = 19991010;
|
||||
|
||||
Person lulu = new Person();
|
||||
lulu.name = "leilei";
|
||||
lulu.birthday = 19991010;
|
||||
|
||||
System.out.println(leilei.equals(lulu));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
equals()方法需要注意的几个点
|
||||
1.参数必须是Object类型,不能是外围类。jdk源码中的equals()方法,传入的参数就是Object类型。
|
||||
2.foo.equals(null) 必须返回false,不能抛NullPointerException。(注意,null instanceof 任意类 总是返回false,因此上面的代码可以运行)
|
||||
3.基本类型比较使用 ==,基本类型数组域的比较使用Arrays.equals()。
|
||||
4.覆盖equals()时,记得要相应地覆盖 hashCode(),与 equals() 保持一致
|
||||
|
||||
hashCode()方法注意的几个点
|
||||
1.当x和y两个对象具有x.equals(y) == true ,你必须要确保x.hashCode() == y.hashCode()。
|
||||
2.根据逆反命题,如果x.hashCode() != y.hashCode(),那么x.equals(y) == false 必定成立。
|
||||
3.你不需要保证,当x.equals(y) == false时,x.hashCode() != y.hashCode()。但是,如果你可以尽可能地使它成立的话,这会提高哈希表的性能。
|
||||
4.hashCode()最简单的合法实现就是简单地return 0;虽然这个实现是正确的,但是这会导致HashMap这些数据结构运行得很慢。
|
||||
|
||||
### 1.2 compareTo()方法实现
|
||||
|
||||
```
|
||||
package leilei.bit.edu.common;
|
||||
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class PersonA implements Comparable<PersonA>{
|
||||
String firstName;
|
||||
String lastName;
|
||||
int birthday;
|
||||
|
||||
public int compareTo(PersonA other) {
|
||||
if(firstName.compareTo(other.firstName) != 0) {
|
||||
return firstName.compareTo(other.firstName);
|
||||
} else if(lastName.compareTo(other.lastName) != 0) {
|
||||
return lastName.compareTo(other.lastName);
|
||||
} else if(birthday < other.birthday) {
|
||||
return -1;
|
||||
} else if(birthday > other.birthday) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "firstName: " + firstName + ",lastName: " + lastName;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
PersonA p1 = new PersonA();
|
||||
p1.firstName = "li";
|
||||
p1.lastName = "lei";
|
||||
|
||||
PersonA p2 = new PersonA();
|
||||
p2.firstName = "han";
|
||||
p2.lastName = "meimei";
|
||||
|
||||
PersonA p3 = new PersonA();
|
||||
p3.firstName = "xiao";
|
||||
p3.lastName = "ming";
|
||||
|
||||
TreeSet tree = new TreeSet();
|
||||
tree.add(p1);
|
||||
tree.add(p2);
|
||||
tree.add(p3);
|
||||
|
||||
for(Object obj:tree) {
|
||||
System.out.println(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
firstName: han,lastName: meimei
|
||||
firstName: li,lastName: lei
|
||||
firstName: xiao,lastName: ming
|
||||
```
|
||||
|
||||
TreeSet在java内部是一种排序的数据结构。根据程序的输出可以看出,person对象在tree中是根据firstName排序的。
|
||||
|
||||
1.总是实现泛型版本 Comparable 而不是实现原始类型 Comparable 。因为这样可以节省代码量和减少不必要的麻烦。
|
||||
2.只关心返回结果的正负号(负/零/正),它们的大小不重要。
|
||||
3.Comparator.compare()的实现与这个类似。
|
||||
|
||||
### 1.3 clone()方法
|
||||
实际中我很少使用clone方法,怕自己的理解有偏差,直接将原文的clone部分摘过来
|
||||
|
||||
```
|
||||
class Values implements Cloneable {
|
||||
String abc;
|
||||
double foo;
|
||||
int[] bars;
|
||||
Date hired;
|
||||
|
||||
public Values clone() {
|
||||
try {
|
||||
Values result = (Values)super.clone();
|
||||
result.bars = result.bars.clone();
|
||||
result.hired = result.hired.clone();
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) { // Impossible
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
1.使用 super.clone() 让Object类负责创建新的对象。
|
||||
2.基本类型域都已经被正确地复制了。同样,我们不需要去克隆String和BigInteger等不可变类型。
|
||||
3.手动对所有的非基本类型域(对象和数组)进行深度复制(deep copy)。
|
||||
4.实现了Cloneable的类,clone()方法永远不要抛CloneNotSupportedException。因此,需要捕获这个异常并忽略它,或者使用不受检异常(unchecked exception)包装它。
|
||||
5.不使用Object.clone()方法而是手动地实现clone()方法是可以的也是合法的。
|
||||
|
||||
## 2.应用类
|
||||
|
||||
### 2.1 StringBuilder 与 StringBuffer
|
||||
这两个类在实际开发中用得很多,举一个很简单的小例子。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void Str_Build() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("I ");
|
||||
sb.append("love");
|
||||
sb.append(" coding");
|
||||
System.out.println(sb.toString());
|
||||
}
|
||||
```
|
||||
|
||||
让代码run起来
|
||||
|
||||
```
|
||||
I love coding
|
||||
```
|
||||
|
||||
注意点:
|
||||
1.不要像这样使用重复的字符串连接:s += item ,因为它的时间效率是O(n^2)。
|
||||
2.使用StringBuilder或者StringBuffer时,可以使用append()方法添加文本和使用toString()方法去获取连接起来的整个文本。示例代码中就是这么做的。
|
||||
3.优先使用StringBuilder,因为它更快。StringBuffer的所有方法都是同步的,而你通常不需要同步的方法。
|
||||
4.StringBuilder的缺陷之一,就是开发过程中,经常用sb作为new出来的对象名称。。。
|
||||
|
||||
### 2.2 生成一个随机整数
|
||||
|
||||
```
|
||||
@Test
|
||||
public void int_random() {
|
||||
Random rand = new Random();
|
||||
int a = rand.nextInt(10) + 1;
|
||||
System.out.println("random num is: " + a);
|
||||
}
|
||||
```
|
||||
|
||||
让代码run起来
|
||||
|
||||
```
|
||||
random num is: 10
|
||||
```
|
||||
|
||||
### 2.3 使用Iterator.remove()
|
||||
|
||||
```
|
||||
@Test
|
||||
public void collections_remove() {
|
||||
List<Integer> list = new ArrayList<Integer>();
|
||||
for(int i=1; i<=10; i++) {
|
||||
list.add(i);
|
||||
}
|
||||
|
||||
Iterator<Integer> it = list.iterator();
|
||||
while(it.hasNext()) {
|
||||
int num = it.next();
|
||||
|
||||
if(num%2 == 0) {
|
||||
System.out.println(num + "===" + num);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
for(int each:list) {
|
||||
System.out.print(each + " ");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
让代码run起来
|
||||
|
||||
```
|
||||
2===2
|
||||
4===4
|
||||
6===6
|
||||
8===8
|
||||
10===10
|
||||
1 3 5 7 9
|
||||
```
|
||||
|
||||
remove()方法作用在next()方法最近返回的条目上。每个条目只能使用一次remove()方法。
|
||||
|
||||
### 2.4 反转字符串
|
||||
c++的程序猿里的一道经典面试题就是反转字符串。对于java或者python等更高级的语言来说,反转字符串都是一句话搞定的事情。
|
||||
|
||||
|
||||
```
|
||||
@Test
|
||||
public void reverse_str() {
|
||||
String raw_str = "some";
|
||||
String rev_str = new StringBuilder(raw_str).reverse().toString();
|
||||
System.out.println("rev_str is: " + rev_str);
|
||||
}
|
||||
```
|
||||
|
||||
让代码run起来
|
||||
|
||||
```
|
||||
rev_str is: emos
|
||||
```
|
||||
|
||||
### 2.5 Thread/Runnable
|
||||
|
||||
```
|
||||
//实现Runnable的方式
|
||||
void startAThread0() {
|
||||
new Thread(new MyRunnable()).start();
|
||||
}
|
||||
|
||||
class MyRunnable implements Runnable {
|
||||
public void run() {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
//继承Thread的方式
|
||||
void startAThread1() {
|
||||
new MyThread().start();
|
||||
}
|
||||
|
||||
class MyThread extends Thread {
|
||||
public void run() {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
匿名继承Thread的方式
|
||||
void startAThread2() {
|
||||
new Thread() {
|
||||
public void run() {
|
||||
...
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
```
|
||||
|
||||
不要直接调用run()方法。总是调用Thread.start()方法,这个方法会创建一条新的线程并使新建的线程调用run()。
|
||||
|
||||
### 2.6 try-finally
|
||||
|
||||
```
|
||||
IO流的例子
|
||||
void writeStuff() throws IOException {
|
||||
OutputStream out = new FileOutputStream(...);
|
||||
try {
|
||||
out.write(...);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
锁的例子
|
||||
void doWithLock(Lock lock) {
|
||||
lock.acquire();
|
||||
try {
|
||||
...
|
||||
} finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1.如果try之前的语句运行失败并且抛出异常,那么finally语句块就不会执行。但无论怎样,在这个例子里不用担心资源的释放。
|
||||
2.如果try语句块里面的语句抛出异常,那么程序的运行就会跳到finally语句块里执行尽可能多的语句,然后跳出这个方法(除非这个方法还有另一个外围的finally语句块)。
|
||||
|
||||
## 3.输入输出
|
||||
### 3.1 从输入流中读取字节数据
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test1() throws Exception {
|
||||
InputStream in = new FileInputStream(new File("xxx"));
|
||||
try {
|
||||
while(true) {
|
||||
int n = in.read();
|
||||
if(n == -1) {
|
||||
break;
|
||||
} else {
|
||||
System.out.println(n);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
112
|
||||
97
|
||||
99
|
||||
107
|
||||
97
|
||||
103
|
||||
101
|
||||
...
|
||||
```
|
||||
|
||||
由此可见,read()方法要么返回下一次从流里读取的字节数(0到255,包括0和255),要么在达到流的末端。
|
||||
|
||||
### 3.2 从输入流中读取块数据
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test2() throws Exception {
|
||||
InputStream in = new FileInputStream(new File("xxx"));
|
||||
try {
|
||||
byte[] buf = new byte[100];
|
||||
while(true) {
|
||||
int n = in.read(buf);
|
||||
if(n == -1) {
|
||||
break;
|
||||
} else {
|
||||
System.out.println(n);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
100
|
||||
100
|
||||
100
|
||||
100
|
||||
100
|
||||
100
|
||||
81
|
||||
```
|
||||
|
||||
要记住的是,read()方法不一定会填满整个buf,所以你必须在处理逻辑中考虑返回的长度。
|
||||
|
||||
### 3.3 读取文本文件
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test3() throws Exception {
|
||||
String filename = "xxx";
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filename)),"utf-8"));
|
||||
try {
|
||||
while (true) {
|
||||
String line = br.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
} else {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1.BufferedReader对象的创建显得很冗长。这是因为Java把字节和字符当成两个不同的概念来看待
|
||||
2.你可以使用任何类型的InputStream来代替FileInputStream,比如socket
|
||||
3.当达到流的末端时,BufferedReader.readLine()会返回null。
|
||||
4.要一次读取一个字符,使用Reader.read()方法。
|
||||
5.你可以使用其他的字符编码而不使用UTF-8,但最好不要这样做。
|
||||
|
||||
### 3.4.向文本写文件
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test4() throws Exception {
|
||||
String filename = "xxx";
|
||||
PrintWriter out = new PrintWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(new File(filename)),"utf-8"));
|
||||
try {
|
||||
out.println("hello");
|
||||
out.println(42);
|
||||
out.println("world");
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1.Printwriter对象的创建显得很冗长。这是因为Java把字节和字符当成两个不同的概念来看待
|
||||
2.就像System.out,你可以使用print()和println()打印多种类型的值。
|
||||
3.你可以使用其他的字符编码而不使用UTF-8,但最好不要这样做。
|
||||
|
||||
## 4 预防性(Defensive Checking)检测
|
||||
### 4.1 预防性检测数值
|
||||
|
||||
```
|
||||
package leilei.bit.edu.common;
|
||||
|
||||
public class Checking {
|
||||
|
||||
public static int factorial(int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("Undefined");
|
||||
} else if(n > 13) {
|
||||
throw new ArithmeticException("Reuslt overflow");
|
||||
} else if(n == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return factorial(n-1) * n;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int n = 5;
|
||||
int ret = factorial(n);
|
||||
System.out.println("ret is: " + ret);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
1.不要认为输入的数值都是正数、足够小的数等等。要显式地检测这些条件。
|
||||
2.一个设计良好的函数应该对所有可能性的输入值都能够正确地执行。要确保所有的情况都考虑到了并且不会产生错误的输出(比如溢出)。
|
||||
|
||||
### 4.2 预防性检测对象
|
||||
|
||||
```
|
||||
public int findIndex(List<String> list, String target) {
|
||||
if (list == null || target == null)
|
||||
throw new NullPointerException();
|
||||
...
|
||||
}
|
||||
```
|
||||
1.不要认为对象参数不会为空(null)。要显式地检测这个条件。
|
||||
|
||||
### 4.3预防性检测数组索引
|
||||
|
||||
```
|
||||
public static void outOfBound(int[] b, int index) {
|
||||
if(b==null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if(index < 0 || index >= b.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
不要认为所以给的数组索引不会越界。要显式地检测它。
|
||||
|
||||
### 4.4 预防性检测数组区间
|
||||
|
||||
```
|
||||
public static void errorRange(int[] b, int off, int len) {
|
||||
if(b == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (off < 0 || off > b.length || len < 0 || b.length - off < len) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
不要认为所给的数组区间(比如,从off开始,读取len个元素)是不会越界。要显式地检测它。
|
||||
|
||||
## 5.数组
|
||||
### 5.1 填充数组元素
|
||||
### 5.2 复制一个范围内的数组元素
|
||||
### 5.3 调整数组大小
|
||||
|
||||
具体代码如下:
|
||||
|
||||
```
|
||||
package leilei.bit.edu.bigNum;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Array_Test {
|
||||
|
||||
public static void printArray(int[] a) {
|
||||
for(int i=0; i<a.length; i++) {
|
||||
System.out.print(a[i] + " ");
|
||||
}
|
||||
}
|
||||
|
||||
public static void fill_test() {
|
||||
int[] a = new int[5];
|
||||
Arrays.fill(a,0,5,1);
|
||||
printArray(a);
|
||||
}
|
||||
|
||||
public static void copy_test() {
|
||||
int[] a = {1,2,3,4,5};
|
||||
int[] b = new int[5];
|
||||
System.arraycopy(a, 0, b, 0, a.length);
|
||||
printArray(b);
|
||||
}
|
||||
|
||||
public static void copyOf_test() {
|
||||
int[] a = {1,2,3,4,5};
|
||||
a = Arrays.copyOf(a,10);
|
||||
printArray(a);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
fill_test();
|
||||
System.out.println();
|
||||
copy_test();
|
||||
System.out.println();
|
||||
copyOf_test();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
代码运行结果如下:
|
||||
|
||||
```
|
||||
1 1 1 1 1
|
||||
1 2 3 4 5
|
||||
1 2 3 4 5 0 0 0 0 0
|
||||
```
|
||||
|
||||
## 6.包装
|
||||
以下代码演示了将四个字节包装成一个int,以及将一个int分解成四个字节。
|
||||
|
||||
```
|
||||
package leilei.bit.edu.bigNum;
|
||||
|
||||
public class Pack {
|
||||
|
||||
public static void pack() {
|
||||
byte[] a = {1,2,3,4};
|
||||
int result = (a[0] & 0xFF) << 24
|
||||
|(a[1] & 0xFF) << 16
|
||||
|(a[2] & 0xFF) << 8
|
||||
|(a[3] & 0xFF) << 0;
|
||||
System.out.println("result is: " + result);
|
||||
}
|
||||
|
||||
public static void unpack() {
|
||||
int x = 16909060;
|
||||
byte[] result = {
|
||||
(byte)(x >>> 24),
|
||||
(byte)(x >>> 16),
|
||||
(byte)(x >>> 8),
|
||||
(byte)(x >>> 0)
|
||||
};
|
||||
System.out.print("the unpack result is: ");
|
||||
for(int i=0; i<result.length; i++) {
|
||||
System.out.print(result[i] + " ");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
pack();
|
||||
unpack();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
让代码run起来:
|
||||
|
||||
```
|
||||
result is: 16909060
|
||||
the unpack result is: 1 2 3 4
|
||||
```
|
|
@ -0,0 +1,92 @@
|
|||
比如我们有以下目录
|
||||
|
||||
```
|
||||
|--project
|
||||
|--src
|
||||
|--javaapplication
|
||||
|--Test.java
|
||||
|--file1.txt
|
||||
|--file2.txt
|
||||
|--build
|
||||
|--javaapplication
|
||||
|--Test.class
|
||||
|--file3.txt
|
||||
|--file4.txt
|
||||
```
|
||||
|
||||
|
||||
在上面的目录中,有一个src目录,这是JAVA源文件的目录,有一个build目录,这是JAVA编译后文件(.class文件等)的存放目录
|
||||
那么,我们在Test类中应该如何分别获得
|
||||
file1.txt file2.txt file3.txt file4.txt这四个文件呢?
|
||||
|
||||
首先讲file3.txt与file4.txt
|
||||
file3.txt:
|
||||
方法一:
|
||||
```
|
||||
File file3 = new File(Test.class.getResource("file3.txt").getFile());
|
||||
```
|
||||
|
||||
方法二:
|
||||
|
||||
```
|
||||
File file3 = new File(Test.class.getResource("/javaapplication/file3.txt").getFile());
|
||||
```
|
||||
|
||||
方法三:
|
||||
|
||||
```
|
||||
File file3 = new File(Test.class.getClassLoader().getResource("javaapplication/file3.txt").getFile());
|
||||
```
|
||||
|
||||
|
||||
file4.txt:
|
||||
方法一:
|
||||
|
||||
```
|
||||
File file4 = new File(Test.class.getResource("/file4.txt").getFile());
|
||||
```
|
||||
|
||||
方法二:
|
||||
|
||||
```
|
||||
File file4 = new File(Test.class.getClassLoader().getResource("file4.txt").getFile());
|
||||
```
|
||||
|
||||
|
||||
很好,我们可以有多种方法选择,但是file1与file2文件呢?如何获得?
|
||||
答案是,你只能写上它们的绝对路径,不能像file3与file4一样用class.getResource()这种方法获得,它们的获取方法如下
|
||||
假如整个project目录放在c:/下,那么file1与file2的获取方法分别为
|
||||
file1.txt
|
||||
方法一:
|
||||
|
||||
```
|
||||
File file1 = new File("c:/project/src/javaapplication/file1.txt");
|
||||
```
|
||||
|
||||
方法二:。。。没有
|
||||
|
||||
file2.txt
|
||||
方法一:
|
||||
|
||||
```
|
||||
File file2 = new File("c:/project/src/file2.txt");
|
||||
```
|
||||
|
||||
方法二:。。。也没有
|
||||
|
||||
重点注意地方:
|
||||
总结一下,就是你想获得文件,你得从最终生成的.class文件为着手点,不要以.java文件的路径为出发点,因为真正使用的就是.class,不会拿个.java文件就使用,因为java是编译型语言嘛
|
||||
|
||||
至于getResouce()方法的参数,你以class为出发点,再结合相对路径的概念,就可以准确地定位资源文件了,至于它的根目录嘛,你用不同的IDE build出来是不同的位置下的,不过都是以顶层package作为根目录,比如在Web应用中,有一个WEB-INF的目录,WEB-INF目录里面除了web.xml文件外,还有一个classes目录,没错了,它就是你这个WEB应用的package的顶层目录,也是所有.class的根目录“/”,假如clasaes目录下面有一个file.txt文件,它的相对路径就是"/file.txt",如果相对路径不是以"/"开头,那么它就是相对于.class的路径。。
|
||||
|
||||
还有一个getResourceAsStream()方法,参数是与getResouce()方法是一样的,它相当于你用getResource()取得File文件后,再new InputStream(file)一样的结果。getResource().getFile()得到的是文件的string,例如可以这么写:
|
||||
|
||||
```
|
||||
File file = new File(Demo01.class.getResource("/a.properties").getFile());
|
||||
```
|
||||
|
||||
而getResourceAsStream()方法返回的是一个inputStream,例如可以这么写:
|
||||
|
||||
```
|
||||
InputStream in = Demo01.class.getResourceAsStream("/a.properties");
|
||||
```
|
|
@ -0,0 +1,66 @@
|
|||
## 1.ConcurrentModificationException
|
||||
有如下代码处理ArrayList
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test1() {
|
||||
List<Integer> list = new ArrayList<>();
|
||||
list.add(1);
|
||||
list.add(2);
|
||||
list.add(3);
|
||||
list.add(4);
|
||||
for(int num: list) {
|
||||
if (num == 2) {
|
||||
list.remove(num);
|
||||
}
|
||||
}
|
||||
System.out.println(StringUtils.join(list, ","));
|
||||
}
|
||||
```
|
||||
|
||||
代码运行起来会报如下错误:
|
||||
|
||||
```
|
||||
java.util.ConcurrentModificationException
|
||||
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
|
||||
at java.util.ArrayList$Itr.next(ArrayList.java:859)
|
||||
...
|
||||
```
|
||||
|
||||
## 2.分析
|
||||
https://juejin.im/post/5a992a0d6fb9a028e46e17ef
|
||||
上面的文章分析得挺详细的,就不复制粘贴了。简单总结一下就是:
|
||||
ArrayList抛异常的位置源码如下
|
||||
|
||||
```
|
||||
final void checkForComodification() {
|
||||
if (modCount != expectedModCount)
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
```
|
||||
|
||||
modCount是ArrayList本身的属性,expectedModCount是ArrayList内部类Iterator的属性。
|
||||
|
||||
理论上他们是同步的,但是我们在某些操作的过程中导致会导致他们不一致,比如说在这个例子中,我们调用的是ArrayList.remove()方法,修改了size和modCount属性,但是Itr中的这cursor、expectedModCount却没有发生变化,当增强for循环再次执行的时候,调用的却是Itr中的方法,最终发现了数据不一致。这就是本例ConcurrentModificationException产生的根本原因。
|
||||
|
||||
## 3.解决方案
|
||||
最保险的方案就是在遍历的时候不remove,最后再remove...
|
||||
|
||||
```
|
||||
@Test
|
||||
public void test2() {
|
||||
List<Integer> list = new ArrayList<>();
|
||||
list.add(1);
|
||||
list.add(2);
|
||||
list.add(3);
|
||||
list.add(4);
|
||||
List<Integer> tmp = new ArrayList<>();
|
||||
for(int num: list) {
|
||||
if (num == 2) {
|
||||
tmp.add(num);
|
||||
}
|
||||
}
|
||||
list.removeAll(tmp);
|
||||
System.out.println(StringUtils.join(list, ","));
|
||||
}
|
||||
```
|
|
@ -0,0 +1,197 @@
|
|||
## 1.为什么java8中加入Stream
|
||||
Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。尤其是对于数据从业人员来说,对数据做各种操作转换是再正常不过的需求,基本每天都会用到。例如下面这么一个简单的小需求:求一个集合中字符串长度小于5的数量。
|
||||
在java8之前,我们一般这么做:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void lenIter() {
|
||||
List<String> list = Arrays.asList("java", "scala", "python", "shell", "ruby");
|
||||
int num = 0;
|
||||
for(String lan: list) {
|
||||
if(lan.length() < 5) {
|
||||
num++;
|
||||
}
|
||||
}
|
||||
System.out.println(num);
|
||||
}
|
||||
```
|
||||
|
||||
这段代码逻辑很简单,但是显得很冗长,可读性嘛也就呵呵了。如果用Stream,我们可以这样:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void lenStream() {
|
||||
List<String> list = Arrays.asList("java", "scala", "python", "shell", "ruby");
|
||||
long num = list.parallelStream().filter(x -> x.length() < 5).count();
|
||||
System.out.println(num);
|
||||
}
|
||||
```
|
||||
|
||||
代码量明显减少而且逻辑特别清楚,即使不懂代码的人看到也能猜出来是什么意思。如果大家了解过函数式编程,就会觉得特别亲切自然。
|
||||
|
||||
## 2.什么是Stream
|
||||
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
|
||||
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
|
||||
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
|
||||
|
||||
Stream和Collection的区别主要有:
|
||||
1.stream本身并不存储数据,数据是存储在对应的collection里,或者在需要的时候才生成的;
|
||||
2.stream不会修改数据源,总是返回新的stream;
|
||||
3.stream的操作是懒执行(lazy)的:仅当最终的结果需要的时候才会执行,比如上面的例子中,结果仅需要前3个长度大于7的字符串,那么在找到前3个长度符合要求的字符串后,filter()将停止执行;
|
||||
|
||||
使用stream的步骤如下:
|
||||
1.创建stream;
|
||||
2.通过一个或多个中间操作(intermediate operations)将初始stream转换为另一个stream;
|
||||
3.通过中止操作(terminal operation)获取结果;该操作触发之前的懒操作的执行,中止操作后,该stream关闭,不能再使用了;
|
||||
|
||||
## 3.创建Stream的方法
|
||||
最常用的为使用静态方法创建
|
||||
|
||||
```
|
||||
@Test
|
||||
public void numberStreamConstruct() {
|
||||
IntStream.of(new int[] {1, 2, 3}).forEach(System.out::println);
|
||||
IntStream.range(1, 3).forEach(System.out::println);
|
||||
IntStream.rangeClosed(1, 3).forEach(System.out::println);
|
||||
}
|
||||
```
|
||||
|
||||
## 4.Stream的转换
|
||||
Stream最大的用途就是各种转换了。跟Spark中的Rdd类似,Rdd里面也是各种transfer操作。
|
||||
1.filter操作。即使原stream中满足条件的元素构成新的stream:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void lenStream() {
|
||||
List<String> list = Arrays.asList("java", "scala", "python", "shell", "ruby");
|
||||
long num = list.parallelStream().filter(x -> x.length() < 5).count();
|
||||
System.out.println(num);
|
||||
}
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
```
|
||||
2
|
||||
```
|
||||
|
||||
得到长度小于5的单词个数
|
||||
|
||||
2.map操作。map算是最常用的一种操作了,遍历原stream中的元素,转换后构成新的stream:
|
||||
|
||||
```
|
||||
@Test
|
||||
public void turnUpperCase() {
|
||||
List<String> list = Arrays.asList(new String[] {"a", "b", "c"});
|
||||
List<String> result = list.stream().map(String::toUpperCase).collect(Collectors.toList());
|
||||
result.forEach(x -> System.out.print(x + " "));
|
||||
}
|
||||
```
|
||||
|
||||
3.distinct操作。distinct也是常用的操作之一。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void distinctStream() {
|
||||
Stream<String> distinctStream = Stream.of("bj","shanghai","tianjin","bj","shanghai").distinct();
|
||||
Stream<String> sortedStream = distinctStream.sorted(Comparator.comparing(String::length));
|
||||
sortedStream.forEach(x -> System.out.print(x + " "));
|
||||
}
|
||||
```
|
||||
|
||||
结果如下
|
||||
|
||||
```
|
||||
bj tianjin shanghai
|
||||
```
|
||||
|
||||
4.排序操作。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void sortStream() {
|
||||
Stream<Integer> sortedStream = Stream.of(1,3,7,4,5,8,6,2).sorted();
|
||||
sortedStream.collect(Collectors.toList()).forEach(x -> System.out.print(x + " "));
|
||||
System.out.println();
|
||||
|
||||
Stream<Integer> sortedReverseStream = Stream.of(1,3,7,4,5,8,6,2).sorted(new Comparator<Integer>() {
|
||||
@Override
|
||||
public int compare(Integer o1, Integer o2) {
|
||||
return o1 - o2;
|
||||
}
|
||||
});
|
||||
Stream<Integer> sortedReverseStreamV2 = Stream.of(1,3,7,4,5,8,6,2).sorted((Integer o1, Integer o2) -> o2 - o1);
|
||||
sortedReverseStreamV2.collect(Collectors.toList()).forEach(x -> System.out.print(x + " "));
|
||||
}
|
||||
```
|
||||
|
||||
最终的结果:
|
||||
|
||||
```
|
||||
1 2 3 4 5 6 7 8
|
||||
8 7 6 5 4 3 2 1
|
||||
```
|
||||
|
||||
## 5.reduction操作
|
||||
1.reduction就是从stream中取出结果,是terminal operation,因此经过reduction后的stream不能再使用了。主要包含以下操作: findFirst()/findAny()/allMatch/anyMatch()/noneMatch等等
|
||||
|
||||
```
|
||||
|
||||
@Test
|
||||
public void reductionStream() {
|
||||
Stream<String> wordList = Stream.of("bj","tj","sh","yy","yq").distinct();
|
||||
Optional<String> firstWord = wordList.filter(word -> word.startsWith("y")).findFirst();
|
||||
System.out.println(firstWord.orElse("unknown"));
|
||||
}
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
```
|
||||
yy
|
||||
```
|
||||
|
||||
2. reduce方法。与其他语言里的reduce方法一样的逻辑。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void reduceTest() {
|
||||
Stream<Integer> list = Stream.of(1,2,3,4,5);
|
||||
Optional<Integer> result = list.reduce((x, y) -> x + y);
|
||||
System.out.println(result);
|
||||
}
|
||||
```
|
||||
结果如下:
|
||||
|
||||
```
|
||||
Optional[15]
|
||||
```
|
||||
|
||||
## 6.collect
|
||||
collect()方法可以对stream中的元素进行各种处理后,得到stream中元素的值。并且Collectors接口提供了很方便的创建Collector对象的工厂方法。
|
||||
|
||||
```
|
||||
@Test
|
||||
public void collectTest() {
|
||||
List<String> list = Stream.of("hello", "world", "hello", "java").collect(Collectors.toList());
|
||||
list.forEach(x -> System.out.print(x + " "));
|
||||
System.out.println();
|
||||
Set<String> set = Stream.of("hello", "world", "hello", "java").collect(Collectors.toSet());
|
||||
set.forEach(x -> System.out.print(x + " "));
|
||||
System.out.println();
|
||||
Set<String> treeset = Stream.of("hello", "world", "hello", "java").collect(Collectors.toCollection(TreeSet::new));
|
||||
treeset.forEach(x -> System.out.print(x + " "));
|
||||
System.out.println();
|
||||
String resultStr = Stream.of("hello", "world", "hello", "java").collect(Collectors.joining(","));
|
||||
System.out.println(resultStr);
|
||||
}
|
||||
```
|
||||
|
||||
最后的输出结果为:
|
||||
|
||||
```
|
||||
hello world hello java
|
||||
world java hello
|
||||
hello java world
|
||||
hello,world,hello,java
|
||||
```
|
|
@ -0,0 +1,128 @@
|
|||
<T extends Comparable<? super T>> 这样的类型参数 (Type Parameter) 在 JDK 中或工具类方法中经常能看到。例如在Collections中的sort方法:
|
||||
|
||||
```
|
||||
public static <T extends Comparable<? super T>> void sort(List<T> list) {
|
||||
list.sort(null);
|
||||
}
|
||||
```
|
||||
|
||||
很多人第一眼看到这个函数签名,都会有些疑惑,干嘛搞这么麻烦,这么声明不就好了么
|
||||
|
||||
```
|
||||
<T extends Comparable<T>>
|
||||
```
|
||||
|
||||
为什么非要声明成这么复杂的形式
|
||||
|
||||
```
|
||||
<T extends Comparable<? super T>>
|
||||
```
|
||||
|
||||
经过一番看书,搜索,搞明白了这样声明的目的。
|
||||
|
||||
## 1.<T extends Comparable<T>> 和 <T extends Comparable<? super T>>的简单区别
|
||||
```
|
||||
<T extends Comparable<T>>
|
||||
```
|
||||
类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T。
|
||||
|
||||
```
|
||||
<T extends Comparable<? super T>>
|
||||
```
|
||||
类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T 或 T 的任一父类。这样声明后,T 的实例之间,T 的实例和它的父类的实例之间,可以相互比较大小。
|
||||
|
||||
|
||||
## 2.示例1
|
||||
看一个很多地方都能找到的例子
|
||||
|
||||
```
|
||||
public class Test1 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Demo<GregorianCalendar> p1 = null; // 报错会
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Demo<T extends Comparable<T>> {}
|
||||
```
|
||||
|
||||
上述代码在IDE中会报错:
|
||||
Error:(11, 14) java: 类型参数java.util.GregorianCalendar不在类型变量T的范围内。
|
||||
|
||||
为什么会报上面的错误呢?因为GregorianCalendar并没有实现Comparable接口。
|
||||
如果将上面的代码稍作修改,就能正常运行:
|
||||
|
||||
```
|
||||
public class Test1 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Demo<GregorianCalendar> p1 = null;
|
||||
}
|
||||
}
|
||||
|
||||
class Demo<T extends Comparable<? super T>> {}
|
||||
```
|
||||
|
||||
为什么现在没有问题?因为GregorianCalendar继承了Calendar类,而Calendar实现了Comparable接口。
|
||||
|
||||
通过上面的例子,很明显能看出来: <T extends Comparable<? super T>>这种声明的方式,不仅可以接受T类型,还可以接受T的父类型,这样类型参数对所传入的参数限制更少,提高了 API 的灵活性。
|
||||
|
||||
## 3.示例2
|
||||
看另外一个例子
|
||||
|
||||
```
|
||||
public class Test2 {
|
||||
|
||||
public static <T extends Comparable<T>> void sort1(List<T> list) {
|
||||
Collections.sort(list);
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> void sort2(List<T> list) {
|
||||
Collections.sort(list);
|
||||
}
|
||||
|
||||
|
||||
public static void t1() {
|
||||
List<Animal> animals = new ArrayList<Animal>();
|
||||
animals.add(new Animal(20));
|
||||
animals.add(new Animal(30));
|
||||
|
||||
List<Dog> dogs = new ArrayList<Dog>();
|
||||
dogs.add(new Dog(5));
|
||||
dogs.add(new Dog(10));
|
||||
|
||||
sort1(animals);
|
||||
// sort1(dogs); 会报错
|
||||
|
||||
sort2(animals);
|
||||
sort2(dogs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Animal implements Comparable<Animal> {
|
||||
public int age;
|
||||
|
||||
public Animal(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public int compareTo(Animal other) {
|
||||
return this.age - other.age;
|
||||
}
|
||||
}
|
||||
|
||||
class Dog extends Animal {
|
||||
|
||||
public Dog(int age) {
|
||||
super(age);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面的例子有两个类:Animal类实现了Comparable接口,Dog继承了Animal类。sort1方法的参数为`<T extends Comparable<T>>` ,sort2方法的参数为`<T extends Comparable<? super T>>`。
|
||||
|
||||
sort1方法只能对`List<Animal>`类型的list进行排序,对于`List<Dog>`类型不能排序。
|
||||
sort2方法因为方法签名的参数为`<T extends Comparable<? super T>>`,所以不管是`List<Animal>`类型还是`List<Dog>`类型,都可以!
|
|
@ -0,0 +1,71 @@
|
|||
在java中,如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,如果在函数中改变了副本的 值不会改变原始的值。
|
||||
|
||||
如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的 地址,所以不会改变参数的值。
|
||||
|
||||
传递值的数据类型:八种基本数据类型和String(这样理解可以,但是事实上String也是传递的地址,只是string对象和其他对 象是不同的,string对象是不能被改变的,内容改变就会产生新对象。那么StringBuffer就可以了,但只是改变其内容。不能改变外部变量所指 向的内存地址)。
|
||||
|
||||
传递地址值的数据类型:除String以外的所有复合数据类型,包括数组、类和接口
|
||||
|
||||
为了对上面参数传递有更清楚的认识,可以参考如下的测试代码:
|
||||
|
||||
```
|
||||
public class ParamTransferTest {
|
||||
|
||||
public static void swapnum(int a, int b) {
|
||||
int tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
public String name;
|
||||
public int age;
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public void changeattr(Person p, String name, int age) {
|
||||
p.name = name;
|
||||
p.age = age;
|
||||
}
|
||||
|
||||
public void printp() {
|
||||
System.out.println(this.name + ", " + this.age);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int a = 1, b = 2;
|
||||
System.out.println("before swap, a is: " + a + ", b is: " + b);
|
||||
swapnum(a, b);
|
||||
System.out.println("after swap, a is: " + a + ", b is: " + b);
|
||||
|
||||
Person p = new Person("zhangsan", 15);
|
||||
System.out.println("\nbefore change, person is: ");
|
||||
p.printp();
|
||||
p.changeattr(p, "lisi", 16);
|
||||
System.out.println("after change, person is: ");
|
||||
p.printp();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
将代码run起来以后,得到的结果为:
|
||||
|
||||
```
|
||||
before swap, a is: 1, b is: 2
|
||||
after swap, a is: 1, b is: 2
|
||||
|
||||
before change, person is:
|
||||
zhangsan, 15
|
||||
after change, person is:
|
||||
lisi, 16
|
||||
|
||||
```
|
||||
|
||||
简单总结起来就是:
|
||||
1.如果传入的参数是基本数据类型,方法里的操作不会对原始值做改变。
|
||||
2.如果传入的参数是一个对象或者数组或者接口等复杂类型,方法里的操作可以改变这个对象里面的具体内容,但是不会改变对象的内存地址。(如果方法里没做相应操作的话)
|
|
@ -0,0 +1,122 @@
|
|||
栈是一种常见的数据结构。如果用一句话来概括栈的特点,估计大部分同学都能脱口而出:后进先出,即先进来的元素保存在栈的最底部,新来的元素则在栈顶堆积,直到栈满为止;而取元素的时候,只能从栈顶取,直到栈空为止。整个过程,与摞书的过程很类似:放书的时候都是摞在最上面,取书的时候也是从最上面开始取。要想取出下面的书,就必须先将上面的书先取走。
|
||||
|
||||
原理就讲这么多,本身也比较简单。接下来,照例是咱们的口号:
|
||||
talk is cheap, show me the code
|
||||
|
||||
```
|
||||
package leilei.bit.edu.stacktest;
|
||||
|
||||
/**
|
||||
* @author lei.wang
|
||||
*
|
||||
*/
|
||||
|
||||
public class Stack {
|
||||
|
||||
//存数据的数组
|
||||
int[] data;
|
||||
|
||||
//栈的最大长度
|
||||
private int size;
|
||||
//栈顶的位置
|
||||
private int top;
|
||||
|
||||
public Stack(int size) {
|
||||
this.size = size;
|
||||
data = new int[size];
|
||||
top = -1;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getTop() {
|
||||
return top;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空栈
|
||||
* @return
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return top == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为满栈
|
||||
* @return
|
||||
*/
|
||||
public boolean isFull() {
|
||||
return (top+1) == size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压栈操作
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public boolean push(int data) {
|
||||
if(isFull()) {
|
||||
System.out.println("the stack is full!");
|
||||
return false;
|
||||
} else {
|
||||
top++;
|
||||
this.data[top] = data;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 弹栈操作
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public int pop() throws Exception {
|
||||
if(isEmpty()) {
|
||||
throw new Exception("the stack is empty!");
|
||||
} else {
|
||||
return this.data[top--];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取栈顶的元素,但不弹栈
|
||||
* @return
|
||||
*/
|
||||
public int peek() {
|
||||
return this.data[getTop()];
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Stack stack = new Stack(20);
|
||||
stack.push(0);
|
||||
stack.push(1);
|
||||
stack.push(2);
|
||||
stack.push(3);
|
||||
System.out.println("Now the top_num is:" + stack.peek());
|
||||
|
||||
while(! stack.isEmpty()) {
|
||||
try {
|
||||
System.out.println(stack.pop());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
代码运行结果
|
||||
|
||||
```
|
||||
Now the top_num is:3
|
||||
3
|
||||
2
|
||||
1
|
||||
0
|
||||
```
|
||||
|
||||
代码本身比较简单,就不在过多解释,如果还有不懂的地方请看注释;注释还不懂的话,请留言。
|
|
@ -0,0 +1,93 @@
|
|||
## 1.java泛型中的关键字
|
||||
java泛型中有如下关键字:
|
||||
1. ? 表示通配符类型
|
||||
2. <? extends T> 既然是extends,就是表示泛型参数类型的上界,说明参数的类型应该是T或者T的子类。
|
||||
3. <? super T> 既然是super,表示的则是类型的下界,说明参数的类型应该是T类型的父类,一直到object。
|
||||
|
||||
## 2.示例代码
|
||||
|
||||
```
|
||||
public class GenericClass {
|
||||
|
||||
static class Animal {}
|
||||
|
||||
static class FlyAnimal extends Animal {}
|
||||
|
||||
static class Bird extends FlyAnimal {}
|
||||
|
||||
public void testExtend() {
|
||||
List<? extends FlyAnimal> list = new ArrayList<Bird>();
|
||||
|
||||
//无法安全添加任何具有实际意义的元素
|
||||
//list.add(new Bird());
|
||||
//list.add(new FlyAnimal());
|
||||
list.add(null);
|
||||
Animal animal = list.get(0);
|
||||
Bird bird = (Bird)list.get(0);
|
||||
}
|
||||
|
||||
public void testSuper() {
|
||||
List<? super FlyAnimal> list = new ArrayList<FlyAnimal>();
|
||||
list.add(new FlyAnimal());
|
||||
list.add(new Bird());
|
||||
//list.add(new Animal());
|
||||
//List<? super FlyAnimal> list2 = new ArrayList<Bird>();
|
||||
//FlyAnimal flyAnimal = list.get(0); 不能确定返回类型
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 3.extends分析
|
||||
`List<? extends FlyAnimal>` 表示 “具有任何从FlyAnimal继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。但是可以添加null,因为null可以表示任何类型。
|
||||
|
||||
同时,由于list里面的类型是从FlyAnimal中继承过来的,所以可以安全取出FlyAnimal类型。
|
||||
|
||||
所以最后总结下来是:list的add方法不能添加任何有意思的元素,但是可以接受现有子类型Bird的赋值。同时可以安全取出元素类型。
|
||||
|
||||
## 4.super分析
|
||||
List<? super FlyAnimal> 表示“具有任何FlyAnimal父类的列表”,列表的类型至少是一个 FlyAnimal 类型,因此可以安全的向其中添加FlyAnimal及其子类型。由于List<? super FlyAnimal>中的类型可能是任何FlyAnimal的父类型,无法赋值为FlyAnimal的子类型Bird的List<Bird>.
|
||||
同时,因为List<? super FlyAnimal>表示的类型是FlyAnimal的父类,所以编译器也无法确定返回的类型。
|
||||
|
||||
## 5.泛型类型实际上都是相同的基本类型
|
||||
在泛型接口泛型类泛型方法的定义过程中,经常会见到用T等形式的参数表示泛型的形参,用于接收来自外部使用时传入的类型实参。那么如果传入不同类型的实参,生成的相应对象实例的类型是否一样呢?
|
||||
|
||||
```
|
||||
class GenClazz<T> {
|
||||
private T data;
|
||||
public GenClazz() {}
|
||||
|
||||
public GenClazz(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
定义了如上的测试类,然后写个简单的测试方法
|
||||
|
||||
```
|
||||
public void test1() {
|
||||
GenClazz<String> name = new GenClazz<String>("xiaoming");
|
||||
GenClazz<Integer> age = new GenClazz<Integer>(123);
|
||||
|
||||
System.out.println("name class: " + name.getClass());
|
||||
System.out.println("age class: " + age.getClass());
|
||||
System.out.println(name.getClass() == age.getClass());
|
||||
}
|
||||
```
|
||||
|
||||
最后第三行会输出true!
|
||||
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为GenClazz),当然,在逻辑上我们可以理解成多个不同的泛型类型。
|
||||
|
||||
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦除,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
|
||||
|
||||
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
|
||||
|
||||
## 6.最终小结
|
||||
extends 可用于的返回类型限定,不能用于参数类型限定。
|
||||
super 可用于参数类型限定,不能用于返回类型限定。
|
||||
带有super超类型限定的通配符可以向泛型对易用写入,带有extends子类型限定的通配符可以向泛型对象读取。——《Core Java》
|
|
@ -0,0 +1,112 @@
|
|||
## 1.实现数值类型加法
|
||||
实际开发中,经常有数值类型求和的需求,例如实现int类型的加法:
|
||||
|
||||
```
|
||||
public int add(int num1, int num2) {
|
||||
return num1 + num2;
|
||||
}
|
||||
```
|
||||
|
||||
有时候还需要实现long类型的求和:
|
||||
|
||||
```
|
||||
public long add(long num1, long num2) {
|
||||
return num1 + num2;
|
||||
}
|
||||
```
|
||||
|
||||
如果还需要double类型的求和,需要重新在重载一个输入是double类型的add方法。
|
||||
其实逻辑都是一样的,只是输入参数类型不一样而已,正好在java中数值类型有个公共的父类:Number。所以我们可以这么做:
|
||||
|
||||
```
|
||||
public <T extends Number> double add(T t1, T t2) {
|
||||
double allsum;
|
||||
allsum = t1.doubleValue() + t2.doubleValue();
|
||||
return allsum;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
int int1 = 1;
|
||||
int int2 = 2;
|
||||
System.out.println("Integer sum is: " + add(int1, int2));
|
||||
|
||||
long long1 = 100;
|
||||
long long2 = 200;
|
||||
System.out.println("Long sum is: " + add(long1, long2));
|
||||
|
||||
float f1 = 1.0f;
|
||||
float f2 = 2.0f;
|
||||
System.out.println("Float sum is: " + add(f1, f2));
|
||||
|
||||
double d1 = 1.0;
|
||||
double d2 = 2.0;
|
||||
System.out.println("Double sum is: " + add(d1, d2));
|
||||
}
|
||||
```
|
||||
|
||||
将上述的test方法run起来以后,输出如下:
|
||||
|
||||
```
|
||||
Integer sum is: 3
|
||||
Long sum is: 300
|
||||
Float sum is: 3.0
|
||||
Double sum is: 3.0
|
||||
```
|
||||
|
||||
## 2.实现数值类型集合的加法
|
||||
上面是单个数值类型,推广一下,对于数值类型的集合来说,也经常会有类似的需求,这个时候我们可以也做类似的泛型方法:
|
||||
|
||||
```
|
||||
public class GenericAdd {
|
||||
//extend限制返回时候的类型
|
||||
public static void sumlist(List<? extends Number> list) {
|
||||
double sum = 0.0;
|
||||
|
||||
for(Number each : list) {
|
||||
sum += each.doubleValue();
|
||||
}
|
||||
|
||||
double sum2 = list.stream().mapToDouble(x -> x.doubleValue()).sum();
|
||||
System.out.println(sum);
|
||||
System.out.println(sum2);
|
||||
}
|
||||
|
||||
//super限制添加时候的类型
|
||||
public static void lowerBound(List<? super Number> list) {
|
||||
list.add(new Integer(1));
|
||||
list.add(new Float(2));
|
||||
Integer value1 = (Integer) list.get(0);
|
||||
Float value2 = (Float) list.get(1);
|
||||
System.out.println(value1 + "\t" + value2);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<Number> list = new ArrayList<>();
|
||||
lowerBound(list);
|
||||
System.out.println("---------");
|
||||
|
||||
List<Integer> intList = new ArrayList(){{add(1); add(2); add(3);}};
|
||||
sumlist(intList);
|
||||
System.out.println("---------");
|
||||
|
||||
List<Double> doubleList = new ArrayList(){{add(10.0); add(20.0); add(30.0);}};
|
||||
sumlist(doubleList);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
上面代码的输出结果如下:
|
||||
|
||||
```
|
||||
1 2.0
|
||||
---------
|
||||
6.0
|
||||
6.0
|
||||
---------
|
||||
60.0
|
||||
60.0
|
||||
```
|
||||
|
||||
这样就达到了数值类型集合求和的需求!
|
|
@ -0,0 +1,260 @@
|
|||
## 1.为什么需要泛型
|
||||
我们知道java是属于强类型编程语言。变量在使用之前,需要先进行定义,而定义个变量时必须要指定其数据类型,这样编译器在编译阶段就能将很多类型错误消灭在萌芽状态。
|
||||
|
||||
如果我们有这样一个需求:定义一个坐标类。但是该坐标类的数据类型可能是整数,也可以能是小数,还有可能是字符串。举个例子
|
||||
```
|
||||
x=10; y=15;
|
||||
x="东经116",y="北纬39"
|
||||
x=10.01; y=15
|
||||
```
|
||||
|
||||
坐标有可能是整型,也有可能是字符串类型,还有可能是double类型。如果没有泛型,我们可能会这么做
|
||||
|
||||
```
|
||||
package edu.bit.test;
|
||||
|
||||
public class ObjectType {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Point p = new Point();
|
||||
p.setX(10);
|
||||
p.setY(15);
|
||||
|
||||
//向下转型,此时没有问题,代码能正常运行
|
||||
int x = (Integer)p.getX();
|
||||
int y = (Integer)p.getY();
|
||||
System.out.println(p);
|
||||
|
||||
p.setX("东经116");
|
||||
p.setY("北纬39");
|
||||
|
||||
//此时向下转型会有问题
|
||||
double x1 = (Double)p.getX();
|
||||
double y1 = (Double)p.getY();
|
||||
System.out.println(p);
|
||||
}
|
||||
}
|
||||
|
||||
class Point {
|
||||
Object x = 0;
|
||||
Object y = 0;
|
||||
|
||||
public Object getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(Object x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public Object getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(Object y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将代码run起来
|
||||
|
||||
```
|
||||
Point{x=10, y=15}
|
||||
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
|
||||
at edu.bit.test.ObjectType.main(ObjectType.java:22)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
|
||||
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.lang.reflect.Method.invoke(Method.java:497)
|
||||
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
|
||||
|
||||
Process finished with exit code 1
|
||||
```
|
||||
|
||||
在上面的示例代码中,用的是Object类型。这样,在调用set方法的时候是没有问题的。但是在使用get方法时,因为需要将Object类向下转型成其他类,这样容易带来问题。并且,这种问题在jdk编译阶段还不容易被发现,往往只有在运行的时候才会抛出异常!像我们上面的代码,在IDE中是不会报错的,但是代码run起来以后,会抛出一个ClassCastException的异常!
|
||||
|
||||
## 2.泛型的使用方式
|
||||
将上面的代码改用泛型的方式实现
|
||||
|
||||
```
|
||||
package edu.bit.test;
|
||||
|
||||
public class GenericType {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Point1<Integer,Integer> p1 = new Point1<Integer, Integer>();
|
||||
p1.setX(10);
|
||||
p1.setY(15);
|
||||
System.out.println(p1);
|
||||
|
||||
Point1<String,String> p2 = new Point1<String,String>();
|
||||
p2.setX("东经116");
|
||||
p2.setY("北纬39");
|
||||
System.out.println(p2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Point1<T1,T2> {
|
||||
T1 x;
|
||||
T2 y;
|
||||
|
||||
public T1 getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(T1 x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public T2 getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(T2 y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point1{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将代码run起来
|
||||
|
||||
```
|
||||
Point1{x=10, y=15}
|
||||
Point1{x=东经116, y=北纬39}
|
||||
```
|
||||
|
||||
在上面的例子里,直接将Point1类定义为泛型类。泛型类的定义方法为类名后面用尖括号将<T1,T2>包起来,其中的T1,T2是我们自己定义的类标志符,用来表示数据的类型。并且注意的是,T1,T2只是类型标识的占位符,编译阶段并不会真正确定类型,只有在运行阶段才会替换为真正的数据类型。
|
||||
|
||||
使用泛型的好处是:当我们在是使用泛型的时候,即指定了数据类型,又可以根据需要灵活使用不同数据类型,在使用严谨性与灵活性之间做了一个很好的兼顾。尤其是在各种框架中,泛型的使用非常广泛。
|
||||
|
||||
## 3.限制泛型的使用类型
|
||||
在我们上面的代码中,没有对参数类型做任何限制,可以使用任何类型的参数。然后在很多实际场景中,还是需要对参数类型做一定限制的,只能传递部分参数类型,传递其他类型则会引发错误。例如在前面的Point类中,我们希望用户只能传递数字类型,而不能传递字符串等其他类型:
|
||||
|
||||
```
|
||||
package edu.bit.test;
|
||||
|
||||
public class GenericSmallType {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Point2<Integer,Integer> p1 = new Point2<Integer,Integer>();
|
||||
p1.setX(10);
|
||||
p1.setY(15);
|
||||
System.out.println(p1);
|
||||
|
||||
Point2<Double,Double> p2 = new Point2<Double,Double>();
|
||||
p2.setX(10.0);
|
||||
p2.setY(15.01);
|
||||
System.out.println(p2);
|
||||
}
|
||||
}
|
||||
|
||||
class Point2<T1 extends Number,T2 extends Number> {
|
||||
public T1 x;
|
||||
public T2 y;
|
||||
|
||||
public T1 getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(T1 x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public T2 getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(T2 y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point2{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将代码run起来:
|
||||
|
||||
```
|
||||
Point2{x=10, y=15}
|
||||
Point2{x=10.0, y=15.01}
|
||||
```
|
||||
|
||||
## 4.类型擦除(type erasure)
|
||||
java字节码中不包含有泛型的类型信息。编译器在编译阶段去掉泛型信息,在运行的时候加上类型信息,这个过程被称为类型擦除。
|
||||
我们在使用泛型的过程中,如果没有指定数据类型,那么将会擦除泛型类型。
|
||||
|
||||
```
|
||||
package edu.bit.test;
|
||||
|
||||
public class TypeErasure {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Point3 p1 = new Point3();
|
||||
|
||||
p1.setX("东经116");
|
||||
p1.setY("北纬39");
|
||||
|
||||
//向下转型
|
||||
String longitude = (String)p1.getX();
|
||||
String latitude = (String)p1.getY();
|
||||
|
||||
System.out.println(p1);
|
||||
}
|
||||
}
|
||||
|
||||
class Point3<T1,T2> {
|
||||
public T1 x;
|
||||
public T2 y;
|
||||
|
||||
public T1 getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(T1 x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public T2 getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(T2 y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point3{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
本例中,创建Point3对象的时候没有指定数据类型。编译器在处理的时候,会将所有数据向上变为Object类型。然而这样处理完以后,使用get方法的时候,又需要向下转型。这样的话,跟第一部分没有使用泛型就一样了!
|
|
@ -0,0 +1,264 @@
|
|||
## 1.线程池相关的类
|
||||
1.Executor接口,将任务提交与任务的细节解耦开。Executor接口只有一个execute方法
|
||||
|
||||
```
|
||||
public interface Executor {
|
||||
|
||||
/**
|
||||
* Executes the given command at some time in the future. The command
|
||||
* may execute in a new thread, in a pooled thread, or in the calling
|
||||
* thread, at the discretion of the {@code Executor} implementation.
|
||||
*
|
||||
* @param command the runnable task
|
||||
* @throws RejectedExecutionException if this task cannot be
|
||||
* accepted for execution
|
||||
* @throws NullPointerException if command is null
|
||||
*/
|
||||
void execute(Runnable command);
|
||||
}
|
||||
```
|
||||
|
||||
2.ExcutorService接口,继承了Executor接口,提供Service的管理功能,比如中断,` List<Runnable> shutdownNow();`方法。同事,还提供了·`<T> Future<T> submit(Callable<T> task)`等方法。
|
||||
|
||||
3.ThreadPoolExecutor类,继承了AbstractExecutorService类,AbstractExecutorService类继承了ExcutorService接口,所以ThreadPoolExecutor是ExcutorService的实现,我们平时常用的线程池就是ThreadPoolExecutor创建的。
|
||||
|
||||
4.Excutors类,这是个工厂类,提供了常用的创建ExcutorService的静态工厂方法。里面有个静态类DefaultThreadFactory,DefaultThreadFactory类实现了ThreadFactory。
|
||||
|
||||
5.ThreadFactory接口。只有一个方法newThread。
|
||||
|
||||
```
|
||||
public interface ThreadFactory {
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Thread}. Implementations may also initialize
|
||||
* priority, name, daemon status, {@code ThreadGroup}, etc.
|
||||
*
|
||||
* @param r a runnable to be executed by new thread instance
|
||||
* @return constructed thread, or {@code null} if the request to
|
||||
* create a thread is rejected
|
||||
*/
|
||||
Thread newThread(Runnable r);
|
||||
}
|
||||
```
|
||||
|
||||
## 2.ThreadPoolExecutor
|
||||
我们先看ThreadPoolExecutor的一个构造函数
|
||||
|
||||
```
|
||||
/**
|
||||
* Creates a new {@code ThreadPoolExecutor} with the given initial
|
||||
* parameters and default rejected execution handler.
|
||||
*
|
||||
* @param corePoolSize the number of threads to keep in the pool, even
|
||||
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
|
||||
* @param maximumPoolSize the maximum number of threads to allow in the
|
||||
* pool
|
||||
* @param keepAliveTime when the number of threads is greater than
|
||||
* the core, this is the maximum time that excess idle threads
|
||||
* will wait for new tasks before terminating.
|
||||
* @param unit the time unit for the {@code keepAliveTime} argument
|
||||
* @param workQueue the queue to use for holding tasks before they are
|
||||
* executed. This queue will hold only the {@code Runnable}
|
||||
* tasks submitted by the {@code execute} method.
|
||||
* @param threadFactory the factory to use when the executor
|
||||
* creates a new thread
|
||||
* @throws IllegalArgumentException if one of the following holds:<br>
|
||||
* {@code corePoolSize < 0}<br>
|
||||
* {@code keepAliveTime < 0}<br>
|
||||
* {@code maximumPoolSize <= 0}<br>
|
||||
* {@code maximumPoolSize < corePoolSize}
|
||||
* @throws NullPointerException if {@code workQueue}
|
||||
* or {@code threadFactory} is null
|
||||
*/
|
||||
public ThreadPoolExecutor(int corePoolSize,
|
||||
int maximumPoolSize,
|
||||
long keepAliveTime,
|
||||
TimeUnit unit,
|
||||
BlockingQueue<Runnable> workQueue,
|
||||
ThreadFactory threadFactory) {
|
||||
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
|
||||
threadFactory, defaultHandler);
|
||||
}
|
||||
```
|
||||
|
||||
corePoolSize:线程池核心线程大小 。将一个待执行的任务放到线程池中的时候,如果待执行的任务数小于该参数,线程池就会创建一个线程来执行该任务。反之,不再创建新的线程。在线程池刚刚创建后,线程并不是立即启动的。如果事先调用了prestartCoreThread()或者prestartAllCoreThreads()方法则会立即启动核心线程。
|
||||
|
||||
maximumPoolSize:线程池允许的最大线程数量。
|
||||
|
||||
keepAliveTime:是否允许线程超时退出
|
||||
|
||||
workQueue:用来保存待执行任务的阻塞队列 。包括ArrayBlockingQueue,SynchronousQueue,LinkedBlockingQueue,PriorityBlockingQueue等。
|
||||
|
||||
ThreadFactory:创建线程的工厂类。
|
||||
|
||||
RejectedExecutionHandler:饱和处理策略。
|
||||
|
||||
## 3.饱和处理策略
|
||||
如果使用的是有界队列,有可能出现队列和线程池饱和的情况。上面的RejectedExecutionHandler就是用来处理这种情况的。
|
||||
|
||||
JDK中内置有四种策略来实现
|
||||
1.AbortPolicy 直接抛出异常,defaultHandler就是这种方式。
|
||||
2.CallerRunsPolicy
|
||||
|
||||
```
|
||||
/**
|
||||
* A handler for rejected tasks that runs the rejected task
|
||||
* directly in the calling thread of the {@code execute} method,
|
||||
* unless the executor has been shut down, in which case the task
|
||||
* is discarded.
|
||||
*/
|
||||
public static class CallerRunsPolicy implements RejectedExecutionHandler {
|
||||
/**
|
||||
* Creates a {@code CallerRunsPolicy}.
|
||||
*/
|
||||
public CallerRunsPolicy() { }
|
||||
|
||||
/**
|
||||
* Executes task r in the caller's thread, unless the executor
|
||||
* has been shut down, in which case the task is discarded.
|
||||
*
|
||||
* @param r the runnable task requested to be executed
|
||||
* @param e the executor attempting to execute this task
|
||||
*/
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
||||
if (!e.isShutdown()) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3.DiscardPolicy
|
||||
|
||||
```
|
||||
/**
|
||||
* A handler for rejected tasks that silently discards the
|
||||
* rejected task.
|
||||
*/
|
||||
public static class DiscardPolicy implements RejectedExecutionHandler {
|
||||
/**
|
||||
* Creates a {@code DiscardPolicy}.
|
||||
*/
|
||||
public DiscardPolicy() { }
|
||||
|
||||
/**
|
||||
* Does nothing, which has the effect of discarding task r.
|
||||
*
|
||||
* @param r the runnable task requested to be executed
|
||||
* @param e the executor attempting to execute this task
|
||||
*/
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
直接丢弃的策略
|
||||
|
||||
4.DiscardOldestPolicy
|
||||
|
||||
```
|
||||
/**
|
||||
* A handler for rejected tasks that discards the oldest unhandled
|
||||
* request and then retries {@code execute}, unless the executor
|
||||
* is shut down, in which case the task is discarded.
|
||||
*/
|
||||
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
|
||||
/**
|
||||
* Creates a {@code DiscardOldestPolicy} for the given executor.
|
||||
*/
|
||||
public DiscardOldestPolicy() { }
|
||||
|
||||
/**
|
||||
* Obtains and ignores the next task that the executor
|
||||
* would otherwise execute, if one is immediately available,
|
||||
* and then retries execution of task r, unless the executor
|
||||
* is shut down, in which case task r is instead discarded.
|
||||
*
|
||||
* @param r the runnable task requested to be executed
|
||||
* @param e the executor attempting to execute this task
|
||||
*/
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
||||
if (!e.isShutdown()) {
|
||||
e.getQueue().poll();
|
||||
e.execute(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4.Executors类创建线程池
|
||||
Executors类里面提供了一些静态工厂,生成一些常用的线程池。
|
||||
|
||||
1.newFixedThreadPool:创建固定大小的线程池。线程池的大小一旦达到最大值就会保持不变。
|
||||
|
||||
```
|
||||
public static ExecutorService newFixedThreadPool(int nThreads) {
|
||||
return new ThreadPoolExecutor(nThreads, nThreads,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>());
|
||||
}
|
||||
```
|
||||
|
||||
2.newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程。
|
||||
|
||||
```
|
||||
public static ExecutorService newCachedThreadPool() {
|
||||
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>());
|
||||
}
|
||||
```
|
||||
|
||||
3.newSingleThreadExecutor:顾名思义,创建一个单线程的线程池。
|
||||
|
||||
```
|
||||
/**
|
||||
* Creates an Executor that uses a single worker thread operating
|
||||
* off an unbounded queue. (Note however that if this single
|
||||
* thread terminates due to a failure during execution prior to
|
||||
* shutdown, a new one will take its place if needed to execute
|
||||
* subsequent tasks.) Tasks are guaranteed to execute
|
||||
* sequentially, and no more than one task will be active at any
|
||||
* given time. Unlike the otherwise equivalent
|
||||
* {@code newFixedThreadPool(1)} the returned executor is
|
||||
* guaranteed not to be reconfigurable to use additional threads.
|
||||
*
|
||||
* @return the newly created single-threaded Executor
|
||||
*/
|
||||
public static ExecutorService newSingleThreadExecutor() {
|
||||
return new FinalizableDelegatedExecutorService
|
||||
(new ThreadPoolExecutor(1, 1,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>()));
|
||||
}
|
||||
```
|
||||
|
||||
4.newScheduledThreadPool,相当于创建一个定时任务,实际中相当有用。
|
||||
|
||||
```
|
||||
/**
|
||||
* Creates a thread pool that can schedule commands to run after a
|
||||
* given delay, or to execute periodically.
|
||||
* @param corePoolSize the number of threads to keep in the pool,
|
||||
* even if they are idle
|
||||
* @param threadFactory the factory to use when the executor
|
||||
* creates a new thread
|
||||
* @return a newly created scheduled thread pool
|
||||
* @throws IllegalArgumentException if {@code corePoolSize < 0}
|
||||
* @throws NullPointerException if threadFactory is null
|
||||
*/
|
||||
public static ScheduledExecutorService newScheduledThreadPool(
|
||||
int corePoolSize, ThreadFactory threadFactory) {
|
||||
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
|
||||
}
|
||||
```
|
||||
|
||||
5.newSingleThreadScheduledExecutor:创建一个单线程的线程池
|
||||
|
||||
```
|
||||
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
|
||||
return new DelegatedScheduledExecutorService
|
||||
(new ScheduledThreadPoolExecutor(1));
|
||||
}
|
||||
```
|
|
@ -0,0 +1,281 @@
|
|||
## 1.java中的线程状态
|
||||
在java中,线程通常有五种状态:创建,就绪,运行,阻塞与死亡。
|
||||
1:创建(NEW)
|
||||
在生成线程对象的时候,并没有调用start方法,这是线程的创建状态。
|
||||
2:就绪(RUNABLE)
|
||||
当调用线程对象的start方法以后,线程就进入了就绪状态。但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
|
||||
3:运行(RUNNING)
|
||||
线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
|
||||
4:阻塞(BLOCKED)
|
||||
线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
|
||||
5:死亡(TERMINATED)
|
||||
如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
|
||||
|
||||
|
||||
## 2.Thread中的start()方法与run()方法
|
||||
java中的线程是通过java.lang.Thread类来实现的。JVM启动时会有一个由主方法所定义的线程,同时可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
|
||||
|
||||
看下jdk中的源码
|
||||
|
||||
```
|
||||
/**
|
||||
* If this thread was constructed using a separate
|
||||
* <code>Runnable</code> run object, then that
|
||||
* <code>Runnable</code> object's <code>run</code> method is called;
|
||||
* otherwise, this method does nothing and returns.
|
||||
* <p>
|
||||
* Subclasses of <code>Thread</code> should override this method.
|
||||
*
|
||||
* @see #start()
|
||||
* @see #stop()
|
||||
* @see #Thread(ThreadGroup, Runnable, String)
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
if (target != null) {
|
||||
target.run();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
再参考一下start方法
|
||||
|
||||
```
|
||||
/**
|
||||
* Causes this thread to begin execution; the Java Virtual Machine
|
||||
* calls the <code>run</code> method of this thread.
|
||||
* <p>
|
||||
* The result is that two threads are running concurrently: the
|
||||
* current thread (which returns from the call to the
|
||||
* <code>start</code> method) and the other thread (which executes its
|
||||
* <code>run</code> method).
|
||||
* <p>
|
||||
* It is never legal to start a thread more than once.
|
||||
* In particular, a thread may not be restarted once it has completed
|
||||
* execution.
|
||||
*
|
||||
* @exception IllegalThreadStateException if the thread was already
|
||||
* started.
|
||||
* @see #run()
|
||||
* @see #stop()
|
||||
*/
|
||||
public synchronized void start() {
|
||||
/**
|
||||
* This method is not invoked for the main method thread or "system"
|
||||
* group threads created/set up by the VM. Any new functionality added
|
||||
* to this method in the future may have to also be added to the VM.
|
||||
*
|
||||
* A zero status value corresponds to state "NEW".
|
||||
*/
|
||||
if (threadStatus != 0)
|
||||
throw new IllegalThreadStateException();
|
||||
|
||||
/* Notify the group that this thread is about to be started
|
||||
* so that it can be added to the group's list of threads
|
||||
* and the group's unstarted count can be decremented. */
|
||||
group.add(this);
|
||||
|
||||
boolean started = false;
|
||||
try {
|
||||
start0();
|
||||
started = true;
|
||||
} finally {
|
||||
try {
|
||||
if (!started) {
|
||||
group.threadStartFailed(this);
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
/* do nothing. If start0 threw a Throwable then
|
||||
it will be passed up the call stack */
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
从源码以及当中的注释可以看出,run()其实是个普通方法,只不过当线程调用了start( )方法后,一旦线程被CPU调度,处于运行状态,那么线程才会去调用这个run()方法;
|
||||
同时,run方法就是一个普通的方法,并不是需要调用start()方法以后才能调用run()方法,线程对象可以随时调用run()方法。
|
||||
|
||||
|
||||
## 3.示例
|
||||
|
||||
```
|
||||
public class ThreadDemo1 extends Thread {
|
||||
|
||||
public ThreadDemo1() {
|
||||
this.setName("ThreadDemo1");
|
||||
}
|
||||
|
||||
public static void printThreadInfo() {
|
||||
System.out.println("current thread name is: " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
printThreadInfo();
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Thread t1 = new Thread(new Task(1));
|
||||
Thread t2 = new Thread(new Task(2));
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
public class Task implements Runnable {
|
||||
|
||||
int count;
|
||||
|
||||
public Task(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("count is: " + count);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面的运行结果有可能是:
|
||||
|
||||
```
|
||||
count is: 1
|
||||
count is: 2
|
||||
```
|
||||
也有可能是
|
||||
|
||||
```
|
||||
count is: 2
|
||||
count is: 1
|
||||
```
|
||||
|
||||
但是如果将
|
||||
|
||||
```
|
||||
t1.start();
|
||||
t2.start();
|
||||
```
|
||||
|
||||
换成
|
||||
|
||||
```
|
||||
t1.run();
|
||||
t2.run();
|
||||
```
|
||||
|
||||
那么结果一定是
|
||||
|
||||
```
|
||||
count is: 1
|
||||
count is: 2
|
||||
```
|
||||
|
||||
再看另外一个示例,模拟两个线程运行。
|
||||
|
||||
```
|
||||
public class ThreadDemo2 {
|
||||
public static void main(String[] args) {
|
||||
Thread t1 = new Thread(new C1());
|
||||
Thread t2 = new Thread(new C2());
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
//t1.run();
|
||||
//t2.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class C1 implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
System.out.println(i);
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class C2 implements Runnable {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
for (int j = 0; j > -10; j--) {
|
||||
System.out.println(j);
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果是调用start方法,输出为
|
||||
|
||||
```
|
||||
0
|
||||
0
|
||||
1
|
||||
-1
|
||||
2
|
||||
-2
|
||||
-3
|
||||
3
|
||||
-4
|
||||
4
|
||||
-5
|
||||
5
|
||||
-6
|
||||
6
|
||||
7
|
||||
-7
|
||||
8
|
||||
-8
|
||||
9
|
||||
-9
|
||||
```
|
||||
|
||||
如果调用的是run方法,结果为
|
||||
|
||||
```
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
0
|
||||
-1
|
||||
-2
|
||||
-3
|
||||
-4
|
||||
-5
|
||||
-6
|
||||
-7
|
||||
-8
|
||||
-9
|
||||
```
|
||||
|
||||
## 4.简单小结
|
||||
start方法是用于启动线程的,可以实现并发,而run方法只是一个普通方法,是不能实现并发的,只是在并发执行的时候会调用。
|
|
@ -0,0 +1,53 @@
|
|||
## 1.使用System.in.read
|
||||
此种方法能从控制台接收一个字符,并且将该字符打印出来
|
||||
|
||||
```
|
||||
public static void t1() throws IOException {
|
||||
System.out.println("Enter a Char: ");
|
||||
char c = (char) System.in.read();
|
||||
System.out.println("your char is: " + c);
|
||||
}
|
||||
```
|
||||
|
||||
此方法的缺点显而易见:
|
||||
1.每次只能获取一个字符。
|
||||
2.read方法获取的是int类型,需要根据需求做各种类型转换。
|
||||
|
||||
## 2.使用BufferedReader
|
||||
|
||||
```
|
||||
public static void t2() {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
String str;
|
||||
try {
|
||||
System.out.println("please enter your name: ");
|
||||
String name = br.readLine();
|
||||
System.out.println("Your name is: " + name);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这种方法可以从控制台接受一个字符串,。并且打印出来。但是这种方式对于多次输入也不是很方便。
|
||||
|
||||
## 3.使用Scanner
|
||||
|
||||
```
|
||||
public static void t3() {
|
||||
Scanner sc = new Scanner(System.in);
|
||||
System.out.println("please input your name: ");
|
||||
String name = sc.nextLine();
|
||||
System.out.println("please input your age: ");
|
||||
int age = sc.nextInt();
|
||||
System.out.println("please input your salary: ");
|
||||
float salary = sc.nextFloat();
|
||||
System.out.println("your msg is: ");
|
||||
System.out.println("name: " + name + ", age: " + age + ", salary: " + salary);
|
||||
}
|
||||
```
|
||||
|
||||
从 JDK 5.0 开始,基本类库中增加了java.util.Scanner类,根据它的 API 文档说明,这个类是采用正则表达式进行基本类型和字符串分析的文本扫描器。使用它的Scanner(InputStream source)构造方法,可以传入系统的输入流System.in而从控制台中读取数据。
|
||||
|
||||
|
||||
从这三种方式的对比很容易看出,用Scanner的方式获取数据是最容易与方便的!
|
|
@ -0,0 +1,104 @@
|
|||
## 1.应用场景
|
||||
java中有丰富的集合类,日常开发中几乎时刻需要使用到各种各样的集合类,其中常用的集合类包括有Map,Set,List,Array等等。下面我们就来针对各个集合类的相互转化做一下总结。
|
||||
|
||||
## 2.实测代码
|
||||
二话不说,直接上代码。
|
||||
|
||||
```
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by WangLei on 17-12-18.
|
||||
*/
|
||||
public class CollectionsTest {
|
||||
|
||||
Map<String, Integer> map = new HashMap<String, Integer>() {{
|
||||
put("a", 1);
|
||||
put("b", 2);
|
||||
put("c", 3);
|
||||
}};
|
||||
|
||||
Set<String> set = new HashSet() {{
|
||||
add("a");
|
||||
add("b");
|
||||
add("c");
|
||||
}};
|
||||
|
||||
List<String> list = new ArrayList() {{
|
||||
add("a");
|
||||
add("b");
|
||||
add("c");
|
||||
|
||||
}};
|
||||
|
||||
String[] arr = {"a", "b", "c"};
|
||||
|
||||
@Test
|
||||
public void map2List() {
|
||||
List<String> keyList = new ArrayList(map.keySet());
|
||||
System.out.println("keyList is: " + keyList);
|
||||
//ArrayList有如下构造方法:public ArrayList(Collection<? extends E> c)
|
||||
List<String> valueList = new ArrayList(map.values());
|
||||
System.out.println("valueList is: " + valueList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void map2set() {
|
||||
Set<String> keySet = map.keySet();
|
||||
//HashSet有如下构造方法:public HashSet(Collection<? extends E> c)
|
||||
Set<String> valueSet = new HashSet(map.values());
|
||||
System.out.println("valueSet is: " + valueSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arrayset() {
|
||||
// array -> set
|
||||
Set<String> set = new HashSet(Arrays.asList(arr));
|
||||
System.out.println("set is: " + set);
|
||||
//set -> array
|
||||
String[] resultArray = set.toArray(new String[set.size()]);
|
||||
Arrays.stream(resultArray).forEach(x -> System.out.print(x + " "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listset() {
|
||||
// list -> set
|
||||
Set<String> resultset = new HashSet<>(list);
|
||||
System.out.println("result set is: " + resultset);
|
||||
// set -> list
|
||||
List<String> resultlist = new ArrayList(set);
|
||||
System.out.println("result list is: " + resultlist);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listarray() {
|
||||
// list -> array
|
||||
String[] resultarray = list.toArray(new String[list.size()]);
|
||||
// array -> list
|
||||
List<String> resultlist = Arrays.asList(arr);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
代码中方法的名称就表示了该方法的用途,就不一一解释了。
|
||||
|
||||
## 3.几个注意的小点
|
||||
1.ArrayList,HashSet等集合类都有`public ArrayList(Collection<? extends E> c)`这种形式的构造方法,可以传一个集合进来初始化。
|
||||
|
||||
2.Set,List等接口都有toArray方法,源码如下:
|
||||
|
||||
```
|
||||
<T> T[] toArray(T[] a);
|
||||
```
|
||||
|
||||
2.`Arrays.asList`方法的源码如下:
|
||||
|
||||
```
|
||||
public static <T> List<T> asList(T... a) {
|
||||
return new ArrayList<>(a);
|
||||
}
|
||||
```
|
||||
|
||||
可以直接将数组变为一个ArrayList
|
|
@ -0,0 +1,131 @@
|
|||
单例模式是实际项目中使用最多的一种设计模式,有着非常广泛的使用场景。下面我们就结合一个实际项目中的例子,来说说单例模式的使用方式。
|
||||
|
||||
## 1.经典单例模式之懒汉模式
|
||||
|
||||
```
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Singleton {
|
||||
|
||||
private static Map<String, String> testMap = new HashMap<String, String>();
|
||||
|
||||
private static Singleton instance;
|
||||
|
||||
private Singleton() {
|
||||
initTestMap();
|
||||
}
|
||||
|
||||
public static void initTestMap() {
|
||||
for(int i=0; i<10; i++) {
|
||||
String value = "a" + String.valueOf(i);
|
||||
testMap.put(String.valueOf(i), value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Singleton getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new Singleton();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
这个单例对象里面有一个testMap,我们希望Singleton对象初始化的时候testMap就已经初始化。
|
||||
注意的几个点是:
|
||||
1.instance对象与testMap对象均为static对象,这样可以直接用类名调用。
|
||||
2.在构造方法中,将testMap初始化。
|
||||
|
||||
## 2.饿汉模式
|
||||
|
||||
```
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Singleton {
|
||||
|
||||
private static Map<String, String> testMap = new HashMap<String, String>();
|
||||
|
||||
private static Singleton instance = new Singleton();
|
||||
|
||||
private Singleton() {
|
||||
initTestMap();
|
||||
}
|
||||
|
||||
public static void initTestMap() {
|
||||
for(int i=0; i<10; i++) {
|
||||
String value = "a" + String.valueOf(i);
|
||||
testMap.put(String.valueOf(i), value);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Singleton getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
饿汉模式与懒汉模式相比较起来,一上来就直接将实例初始化,不存在延迟加载的问题。
|
||||
|
||||
上面的两种写法,没有考虑多线程的情况。如果是在多线程的场景下使用,请参考后面的文章。
|
||||
|
||||
## 3.双重锁校验(double checked locking pattern)
|
||||
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
|
||||
|
||||
```
|
||||
public class Singleton {
|
||||
|
||||
private static Singleton instance;
|
||||
|
||||
private Singleton() {}
|
||||
|
||||
public static Singleton getInstance() {
|
||||
if(instance == null) {
|
||||
synchronized(Singleton.class) {
|
||||
if(instance == null) {
|
||||
instance = new Singleton();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
|
||||
|
||||
1.给 instance 分配内存
|
||||
2.调用 Singleton 的构造函数来初始化成员变量
|
||||
3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
|
||||
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
|
||||
|
||||
我们只需要将 instance 变量声明成 volatile 就可以了。
|
||||
|
||||
```
|
||||
public class Singleton {
|
||||
|
||||
private volatile static Singleton instance;
|
||||
|
||||
private Singleton() {}
|
||||
|
||||
public static Singleton getInstance() {
|
||||
if(instance == null) {
|
||||
synchronized(Singleton.class) {
|
||||
if(instance == null) {
|
||||
instance = new Singleton();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
volatile用在多线程,同步变量。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A)
|
||||
|
||||
参考文章:
|
||||
http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
|
|
@ -0,0 +1,48 @@
|
|||
在集合类(List,Map,Set等)的使用时,我们创建一个常量集合或者传递一个常量集合时,一般会这么做:
|
||||
|
||||
```
|
||||
Set<String> set = new HashSet();
|
||||
set.add("1");
|
||||
set.add("2");
|
||||
...
|
||||
```
|
||||
|
||||
或者在类中初始化一个常量集合:
|
||||
|
||||
```
|
||||
privates static final Set<String> set = new HashSet();
|
||||
static {
|
||||
set.add("1");
|
||||
set.add("2");
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
我们可以使用更简单的双括号方法:
|
||||
|
||||
```
|
||||
Set<String> set = new HashSet() {{
|
||||
add("1");
|
||||
add("2");
|
||||
...
|
||||
}}
|
||||
|
||||
removeCodeIn(new HashSet<String>() {{
|
||||
add("XZ13s");
|
||||
add("AB21/X");
|
||||
add("YYLEX");
|
||||
add("AR5E");
|
||||
}});
|
||||
```
|
||||
|
||||
这两个大括号的含义分别是:
|
||||
第一个括号创建了一个新的匿名内部类;
|
||||
第二个括号声明了匿名内部类实例化时运行的实例初始化块。
|
||||
|
||||
如果你只是要创建并初始化一个实例而不是创建一个新类,或者创建任何不添加字段属性或重载方法的匿名类时,用双括号语法就很好了。
|
||||
|
||||
如果你用的是集合类且该类有构造器参数接受另一个集合生成该集合的实例,那么有个更好的更惯用的替代方法。例如List可以用Arrays.asList来初始化:
|
||||
|
||||
```
|
||||
List<String> list = new ArrayList<String>(Arrays.asList("1", "2", "3"));
|
||||
```
|
|
@ -0,0 +1,138 @@
|
|||
字符串匹配是日常开发中经常使用的case,比如在java中就有indexOf方法。我们现在试着来自己实现一下字符串匹配的算法。
|
||||
|
||||
## 1.暴力循环法
|
||||
暴力算法自然就是双层循环了。具体来说,对于长度为n的源字符串S与长度为m的模式匹配串P,在S中是否存在一个i,当0<i<n-m+1时,使得S[i,...i+m-1] = P[0,...m-1]。如果存在i,则匹配成功,不存在则匹配失败。一旦匹配过程中不成功,就需要让模式匹配串P相对于源字符串S右移一位,或者说让元字符串S的指针i向右移一维,i需要进行回溯。不难看出,整个过程的时间复杂度为O(m*n)
|
||||
|
||||
直接看代码:
|
||||
|
||||
```
|
||||
interface StringMatcher {
|
||||
|
||||
int indexOf(String source, String pattern);
|
||||
}
|
||||
|
||||
class ViolentStringMatcher implements StringMatcher {
|
||||
|
||||
@Override
|
||||
public int indexOf(String source, String pattern) {
|
||||
int i = 0, j = 0;
|
||||
int sLen = source.length(), pLen = pattern.length();
|
||||
char[] src = source.toCharArray();
|
||||
char[] pat = pattern.toCharArray();
|
||||
while(i < sLen && j < pLen) {
|
||||
if(src[i] == pat[j]) {
|
||||
i++;
|
||||
j++;
|
||||
} else {
|
||||
// 如果当前字符匹配不成功,则i回溯到此次匹配最开始的位置+1处,也就是i = i - j + 1
|
||||
// (因为i,j是同步增长的), j = 0;
|
||||
i = i - j + 1;
|
||||
j = 0;
|
||||
}
|
||||
}
|
||||
if(j == pLen) {
|
||||
return i - j;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String source = "abcdefg";
|
||||
String pattern = "bc";
|
||||
ViolentStringMatcher matcher = new ViolentStringMatcher();
|
||||
int index = matcher.indexOf(source, pattern);
|
||||
System.out.println(index);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后代码的输出结果为:
|
||||
|
||||
```
|
||||
1
|
||||
```
|
||||
|
||||
|
||||
## 2.KMP算法
|
||||
在字符串S中寻找P,当匹配到位置i时两个字符串不相等,这时我们需要将字符串f向前移动。常规方法是每次向前移动一位,但是它没有考虑前i-1位已经比较过这个事实,所以效率不高。事实上,如果我们提前计算某些信息,就有可能一次前移多位。KMP算法就是这个思想。
|
||||
|
||||
具体KMP算法的讲解请看参考文献,直接上代码。
|
||||
|
||||
```
|
||||
interface StringMatcher {
|
||||
|
||||
int indexOf(String source, String pattern);
|
||||
}
|
||||
|
||||
|
||||
class KMPMatcher implements StringMatcher {
|
||||
|
||||
// 已知next[j] = k,利用递归的思想求出next[j+1]的值
|
||||
// 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
|
||||
// 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
|
||||
// 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
|
||||
// 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
|
||||
private int[] getNext(char[] p) {
|
||||
int pLen = p.length;
|
||||
int[] next = new int[pLen];
|
||||
int k = -1;
|
||||
int j = 0;
|
||||
next[0] = -1;
|
||||
while(j < pLen - 1) {
|
||||
if(k == -1 || p[j] == p[k]) {
|
||||
k++;
|
||||
j++;
|
||||
next[j] = k;
|
||||
} else {
|
||||
k = next[k];
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int indexOf(String source, String pattern) {
|
||||
int i = 0, j = 0;
|
||||
char[] src = source.toCharArray();
|
||||
char[] pat = pattern.toCharArray();
|
||||
int sLen = src.length;
|
||||
int pLen = pat.length;
|
||||
int[] next = getNext(pat);
|
||||
while (i < sLen && j < pLen) {
|
||||
// 如果j = -1,或者当前字符匹配成功(src[i] = ptn[j]),都让i++,j++
|
||||
if (j == -1 || src[i] == pat[j]) {
|
||||
i++;
|
||||
j++;
|
||||
} else {
|
||||
// 如果j!=-1且当前字符匹配失败,则令i不变,j=next[j],即让pattern模式串右移j-next[j]个单位
|
||||
j = next[j];
|
||||
}
|
||||
}
|
||||
if (j == pLen)
|
||||
return i - j;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String source = "abcdefg";
|
||||
String pattern = "bc";
|
||||
KMPMatcher kmpMatcher = new KMPMatcher();
|
||||
int result = kmpMatcher.indexOf(source, pattern);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
最后的输出结果:
|
||||
|
||||
```
|
||||
1
|
||||
```
|
||||
|
||||
## 参考理解:
|
||||
1.http://blog.csdn.net/buaa_shang/article/details/9907183
|
||||
2.http://kenby.iteye.com/blog/1025599
|
||||
3.https://juejin.im/entry/58edc67461ff4b006925d2e9 很详细的 KMP 算法讲解,逻辑清晰,易懂
|
|
@ -0,0 +1,204 @@
|
|||
我们经常会听说IoC,也就是Inversion of Controller,控制反转。事实上,IoC并不是一个新鲜的概念,最早可能是在1988年,由Ralph E. Johnson和Brian Foote在论文Designing Reusable Classes中提出。IoC从字面上来说有两个内容,一个是控制,一个是反转。那么什么是控制呢?又是怎样反转的呢?
|
||||
|
||||
为了更好的理解,我们用个实例来说明吧。下面的程序有一个Example类,它含有一个doStuff()方法来完成某件事情。注意,以下的代码都是截取的片段,并不完整。
|
||||
|
||||
如果采用传统的编程:
|
||||
Example.java
|
||||
|
||||
```
|
||||
public class Example {
|
||||
|
||||
private DataFinder dataFinder;
|
||||
|
||||
public Example(){
|
||||
dataFinder = new MysqlDataFinder();
|
||||
}
|
||||
|
||||
public void doStuff() {
|
||||
// 此处具体实现省略
|
||||
...
|
||||
dataFinder.getData();
|
||||
...
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
此外提供一个DataFinder接口:
|
||||
DataFinder.java
|
||||
|
||||
```
|
||||
public interface DataFinder {
|
||||
void getData();
|
||||
}
|
||||
```
|
||||
|
||||
MysqlDataFinder实现:
|
||||
|
||||
MysqlDataFinder.java
|
||||
|
||||
```
|
||||
public class MysqlDataFinder implements DataFinder{
|
||||
|
||||
@Override
|
||||
public void getData() {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后有个Main方法:
|
||||
Client.java
|
||||
|
||||
```
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args){
|
||||
|
||||
Example example = new Example();
|
||||
example.doStuff();
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在问题来了,因为DataFinder接口有很多不同的实现,譬如可以读取Mysql数据库中的数据MysqlDataFinder,还可能读取Oracle数据库中的数据的OracleDataFinder。它们都实现了DataFinder接口,并有各自的getData()实现。我们要想读取不同的数据源中的数据,就需要实现不同的Example类:
|
||||
|
||||
```
|
||||
public class Example1 {
|
||||
|
||||
private DataFinder dataFinder;
|
||||
|
||||
public Example1(){
|
||||
dataFinder = new MysqlDataFinder();
|
||||
}
|
||||
|
||||
public void doStuff() {
|
||||
// 此处具体实现省略
|
||||
...
|
||||
dataFinder.getData();
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
public class Example2 {
|
||||
|
||||
private DataFinder dataFinder;
|
||||
|
||||
public Example2(){
|
||||
dataFinder = new OracleDataFinder();
|
||||
}
|
||||
|
||||
public void doStuff() {
|
||||
// 此处具体实现省略
|
||||
...
|
||||
dataFinder.getData();
|
||||
...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args){
|
||||
|
||||
Example example1 = new Example1();
|
||||
example1.doStuff();
|
||||
|
||||
Example example2 = new Example2();
|
||||
example2.doStuff();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/ioc/1.png)
|
||||
|
||||
Example既依赖于接口DataFinder,又同时依赖实现,因为它需要在编译阶段就确定使用哪种实现,这样显然缺乏灵活性,会产生很多重复的代码,有没有更好的方法减少重复呢?我们首先想到,可以在Example构造器中给个参数,来控制实现的DataFinder类型:
|
||||
|
||||
```
|
||||
public class Example {
|
||||
|
||||
private DataFinder dataFinder;
|
||||
|
||||
public Example(){
|
||||
}
|
||||
|
||||
public Example(int type){
|
||||
if(type == 1){
|
||||
dataFinder = new MysqlDataFinder();
|
||||
}else if(type == 2){
|
||||
dataFinder = new OracleDataFinder();
|
||||
}
|
||||
}
|
||||
|
||||
public void doStuff() {
|
||||
// 此处具体实现省略
|
||||
...
|
||||
dataFinder.getData();
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args){
|
||||
|
||||
Example example1 = new Example(1);
|
||||
example1.doStuff();
|
||||
|
||||
Example example2 = new Example(2);
|
||||
example2.doStuff();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在代码变得简单了,但Example类依然依赖具体的实现,实际上Example并不应该有这么多的控制逻辑,它应该只负责调用doStuff()方法来完成工作,至于用什么类型的DataFinder不应该是它考虑的问题。我们试着将控制调用哪种类型DataFinder的任务交给Client类。
|
||||
|
||||
```
|
||||
public class Example {
|
||||
|
||||
private DataFinder dataFinder;
|
||||
|
||||
public Example(){
|
||||
}
|
||||
|
||||
public Example(DataFinder dataFinder){
|
||||
this.dataFinder = dataFinder;
|
||||
}
|
||||
|
||||
public void doStuff() {
|
||||
// 此处具体实现省略
|
||||
...
|
||||
dataFinder.getData();
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args){
|
||||
|
||||
Example example1 = new Example(new MysqlDataFinder());
|
||||
example1.doStuff();
|
||||
|
||||
Example example2 = new Example(new OracleDataFinder());
|
||||
example2.doStuff();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
![这里写图片描述](https://github.com/bitcarmanlee/easy-algorithm-interview-photo/blob/master/languages/java/ioc/2.png)
|
||||
|
||||
这样Example不用依赖具体的实现了,不用管到底是用哪种类型的DataFinder。换句话说,就是将调用类Example类对于选择哪个具体DataFinder的控制权从其中移除,转交给Client决定,实现了“控制”的“反转”,这也就是我们所说的IoC。
|
||||
|
||||
我们现在知道了控制反转,我们可以把它看作是一个概念。而依赖注入(Dependency Injection)是控制反转的一种实现方法。James Shore给出了依赖注入的定义:依赖注入就是将实例变量传入到一个对象中去(Dependency injection means giving an object its instance variables)。
|
||||
|
||||
Spring的核心就是依赖注入。Spring支持的注入方式主要有两种:setter注入(setter injection)和构造器注入(constructor injection)。我们上面的代码便是使用的构造器注入。关于注入的这两种方式请查看后续章节。
|
||||
|
||||
此外,对于控制反转还有另外一种实现: service locator。有兴趣的可以研究一下。
|
||||
|
||||
传统编程和IoC的对比
|
||||
1.传统编程:决定使用哪个具体的实现类的控制权在调用类本身,在编译阶段就确定了。
|
||||
2.IoC模式:调用类只依赖接口,而不依赖具体的实现类,减少了耦合。控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。
|
||||
|
||||
注:这是博主本人在网络上找了比较长的时间找到的一个关于控制反转与依赖注入比较好的一个文章。没有找到原文的出处,所以没给出具体的链接地址。
|
|
@ -0,0 +1,189 @@
|
|||
关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结。此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制。在撰写本文时,既参考了Thinking in Java, Effective Java,JavaWorld,developerWorks中的相关文章和其它网络资料,也加入了自己的实践经验与理解,文、码并茂,希望对大家有所帮助。(2012.02.14最后更新)
|
||||
|
||||
## 1. 什么是Java对象序列化
|
||||
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
|
||||
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
|
||||
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用,在本文的后续章节中将会陆续讲到。
|
||||
|
||||
## 2. 简单示例
|
||||
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。此处将创建一个可序列化的类Person,本文中的所有示例将围绕着该类或其修改版。
|
||||
Gender类,是一个枚举类型,表示性别
|
||||
|
||||
```
|
||||
public enum Gender {
|
||||
MALE,FEMALE
|
||||
}
|
||||
```
|
||||
|
||||
Person类,实现了Serializable接口,它包含三个字段:name,String类型;age,Integer类型;gender,Gender类型。另外,还重写该类的toString()方法,以方便打印Person实例中的内容。
|
||||
|
||||
```
|
||||
public class Person implements Serializable{
|
||||
|
||||
private String name = null;
|
||||
|
||||
transient private Integer age = null;
|
||||
|
||||
private Gender gender = null;
|
||||
|
||||
public Person() {
|
||||
System.out.println("none-arg constructor");
|
||||
}
|
||||
|
||||
public Person(String name,Integer age,Gender gender) {
|
||||
System.out.println("arg constructor");
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(Gender gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + name + "," + age + "," + gender + "]";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
SimpleSerial,是一个简单的序列化程序,它先将一个Person对象保存到文件person.out中,然后再从该文件中读出被存储的Person对象,并打印该对象。
|
||||
|
||||
```
|
||||
public class SimpleSerial {
|
||||
|
||||
public static void main(String[] args) throws Exception{
|
||||
File file = new File("person.out");
|
||||
|
||||
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
|
||||
Person person = new Person("John",18,Gender.MALE);
|
||||
out.writeObject(person);
|
||||
out.close();
|
||||
|
||||
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
|
||||
Object newPerson = in.readObject();
|
||||
in.close();
|
||||
System.out.println(newPerson);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上述程序的输出的结果为:
|
||||
|
||||
```
|
||||
arg constructor
|
||||
[John, 31, MALE]
|
||||
```
|
||||
|
||||
此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
|
||||
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
|
||||
|
||||
## 3. Serializable的作用
|
||||
为什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
|
||||
|
||||
```
|
||||
private void writeObject0(Object obj, boolean unshared) throws IOException {
|
||||
|
||||
if (obj instanceof String) {
|
||||
writeString((String) obj, unshared);
|
||||
} else if (cl.isArray()) {
|
||||
writeArray(obj, desc, unshared);
|
||||
} else if (obj instanceof Enum) {
|
||||
writeEnum((Enum) obj, desc, unshared);
|
||||
} else if (obj instanceof Serializable) {
|
||||
writeOrdinaryObject(obj, desc, unshared);
|
||||
} else {
|
||||
if (extendedDebugInfo) {
|
||||
throw new NotSerializableException(cl.getName() + "\n"
|
||||
+ debugInfoStack.toString());
|
||||
} else {
|
||||
throw new NotSerializableException(cl.getName());
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
|
||||
|
||||
## 4. 默认序列化机制
|
||||
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
|
||||
|
||||
## 5. 影响序列化
|
||||
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
|
||||
|
||||
### 5.1 transient关键字
|
||||
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Person类中的age字段声明为transient,如下所示,
|
||||
|
||||
```
|
||||
public class Person implements Serializable {
|
||||
...
|
||||
transient private Integer age = null;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
再执行SimpleSerial应用程序,会有如下输出:
|
||||
|
||||
```
|
||||
arg constructor
|
||||
[John, null, MALE]
|
||||
```
|
||||
|
||||
可见,age字段未被序列化。
|
||||
|
||||
### 5.2 writeObject()方法与readObject()方法
|
||||
对于上述已被声明为transitive的字段age,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:
|
||||
|
||||
```
|
||||
public class Person implements Serializable {
|
||||
...
|
||||
transient private Integer age = null;
|
||||
...
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.defaultWriteObject();
|
||||
out.writeInt(age);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
age = in.readInt();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。
|
||||
|
||||
再次执行SimpleSerial应用程序,则又会有如下输出:
|
||||
|
||||
```
|
||||
arg constructor
|
||||
[John, 31, MALE]
|
||||
```
|
||||
|
||||
必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。
|
||||
|
||||
|
||||
原文链接地址:http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html
|
|
@ -0,0 +1,40 @@
|
|||
## 1.jar包中的资源文件
|
||||
在日常项目中,经常会将一些资源文件或者配置文件打入最终的jar包中。我们可以查看jar包中是否包含这个文件,但是无法看到jar包中这个文件的具体内容。这个时候怎么办呢?下面教大家一种比较简单快捷的方式。
|
||||
|
||||
## 2.读取jar包中的资源文件
|
||||
假设项目A中,resources里有个zookeeper.properties的文件。打包的时候,这个zookeeper.properties自然被打入了A.jar,而且是在classpath里。
|
||||
现在我们的需求是从这个A.jar中查看zookeeper.properties的具体内容,步骤如下。
|
||||
1.新起一个maven工程,这个工程只需将项目A导入dependency里即可,这样A.jar就会在External Libraries里。
|
||||
2.然后新建一个类。代码如下
|
||||
|
||||
```
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Created by WangLei on 18-9-25.
|
||||
*/
|
||||
public class ReadZkConfig {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
InputStream stream = ReadZkConfig.class.getClassLoader().getResourceAsStream("zookeeper.properties");
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
|
||||
String line;
|
||||
try {
|
||||
while ((line = br.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
注意jar包中的资源文件无法以file的方式读取,需要用stream的方式读取。
|
Loading…
Reference in New Issue