pull/1/head
wanglei 2020-08-12 21:10:51 +08:00
parent 5768a06296
commit 118623ff4d
32 changed files with 4376 additions and 0 deletions

View File

@ -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());
}
}
```

View File

@ -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));
```

View File

@ -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_FACTORsize / 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到6464到128浪费的空间会越来越大。
而如果该值设置为1则每次空间使用完毕才会扩容put时候操作耗时会增加。
所以0.75是时间与空间的一个平衡。
## 4.put时候操作
1.7插入元素到单链表中采用头插入法1.8采用的是尾插入法
## 5.HashMap死循环
HashMap在并发执行put操作时会引起死循环是因为多线程会导致HashMap的Entry链表形成环形数据结构。

View File

@ -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

View File

@ -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数组中完成的。

View File

@ -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 还没有完成初始化。

View File

@ -0,0 +1,70 @@
## 1.可变参数的定义
从JDK1.5之后java就提供了变长参数(variable argumentsvarargs)。我们在定义方法的时候,可以使用不确定个数的参数。对于同一个方法,也可以通过不确定参数个数的方式进行重载。首先来看个最简单的例子:
```
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方法所以我们在实际编码过程中避免带有变长参数方法的重载

View File

@ -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

View File

@ -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类的实例其实例为类被加载后的字节码。

View File

@ -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!
```

View File

@ -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
}
```

View File

@ -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
```
代码本身比较简单,也没太多技术含量,就当个笔记吧。

View File

@ -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
```

View File

@ -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");
```

View File

@ -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, ","));
}
```

View File

@ -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
```

View File

@ -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>`类型,都可以!

View File

@ -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.如果传入的参数是一个对象或者数组或者接口等复杂类型,方法里的操作可以改变这个对象里面的具体内容,但是不会改变对象的内存地址。(如果方法里没做相应操作的话)

View File

@ -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
```
代码本身比较简单,就不在过多解释,如果还有不懂的地方请看注释;注释还不懂的话,请留言。

View File

@ -0,0 +1,93 @@
## 1.java泛型中的关键字
java泛型中有如下关键字
1. ? 表示通配符类型
2. &lt;? extends T> 既然是extends就是表示泛型参数类型的上界说明参数的类型应该是T或者T的子类。
3. &lt;? 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&lt;? super FlyAnimal> 表示“具有任何FlyAnimal父类的列表”列表的类型至少是一个 FlyAnimal 类型因此可以安全的向其中添加FlyAnimal及其子类型。由于List&lt;? super FlyAnimal>中的类型可能是任何FlyAnimal的父类型无法赋值为FlyAnimal的子类型Bird的List&lt;Bird>.
同时因为List&lt;? 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》

View File

@ -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
```
这样就达到了数值类型集合求和的需求!

View File

@ -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方法的时候又需要向下转型。这样的话跟第一部分没有使用泛型就一样了

View File

@ -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的静态工厂方法。里面有个静态类DefaultThreadFactoryDefaultThreadFactory类实现了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用来保存待执行任务的阻塞队列 。包括ArrayBlockingQueueSynchronousQueueLinkedBlockingQueuePriorityBlockingQueue等。
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));
}
```

View File

@ -0,0 +1,281 @@
## 1.java中的线程状态
在java中线程通常有五种状态创建就绪运行阻塞与死亡。
1创建(NEW)
在生成线程对象的时候并没有调用start方法这是线程的创建状态。
2就绪(RUNABLE)
当调用线程对象的start方法以后线程就进入了就绪状态。但是此时线程调度程序还没有把该线程设置为当前线程此时处于就绪状态。在线程运行之后从等待或者睡眠中回来之后也会处于就绪状态。
3运行(RUNNING)
线程调度程序将处于就绪状态的线程设置为当前线程此时线程就进入了运行状态开始运行run函数当中的代码。
4阻塞(BLOCKED)
线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspendwait等方法都可以导致线程阻塞。
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方法只是一个普通方法是不能实现并发的只是在并发执行的时候会调用。

View File

@ -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的方式获取数据是最容易与方便的

View File

@ -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.SetList等接口都有toArray方法源码如下
```
<T> T[] toArray(T[] a);
```
2.`Arrays.asList`方法的源码如下:
```
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
```
可以直接将数组变为一个ArrayList

View File

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

View File

@ -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"));
```

View File

@ -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]iPSSiiO(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 算法讲解,逻辑清晰,易懂

View File

@ -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模式调用类只依赖接口而不依赖具体的实现类减少了耦合。控制权交给了容器在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。
注:这是博主本人在网络上找了比较长的时间找到的一个关于控制反转与依赖注入比较好的一个文章。没有找到原文的出处,所以没给出具体的链接地址。

View File

@ -0,0 +1,189 @@
关于Java序列化的文章早已是汗牛充栋了本文是对我个人过往学习理解及应用Java序列化的一个总结。此文内容涉及Java序列化的基本原理以及多种方法对序列化形式进行定制。在撰写本文时既参考了Thinking in Java, Effective JavaJavaWorlddeveloperWorks中的相关文章和其它网络资料也加入了自己的实践经验与理解文、码并茂希望对大家有所帮助。(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接口它包含三个字段nameString类型ageInteger类型genderGender类型。另外还重写该类的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

View File

@ -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的方式读取。