我的个人博客

哪有什么胜利可言,挺着就代表着一切


  • 首页

  • 归档

java集合---Set

发表于 2019-03-30 | 分类于 java

如图:

Set

Set

不包含重复元素的集合(通过equals来判断),Set中只能包含一个null.当Set中存入
可变元素时,要特别注意,因为我们可能改变可变元素的值,从而影响对象相等的变更。

SortedSet

SortedSet接口定义了对元素进行自然排序的方法,实现它的类用对内部元素进行排序(这里的自然排序指的是升序排序)。

NavigableSet

NavigableSet扩展了 SortedSet,具有了为给定搜索目标报告最接近匹配项的导航方法。
方法 lower、floor、ceiling 和 higher 分别返回小于、小于等于、大于等于、大于给定
元素的元素,如果不存在这样的元素,则返回 null

AbstractSet

AbstractSet实现Set,是一个抽象类。AbstractSet只实现了equals(Object o),hashCode(),removeAll(Collection<?> c)
方法。

EnumSet

EnumSet 是一个专为枚举设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet
时显式或隐式地指定。

HashSet

HashSet依赖哈希表,它不保证集合中元素的顺序,即不能保证迭代的顺序与插入的顺序一致。HashSet允许null.

LinkedHashSet

LinkedHashSet继承HashSet,它与HashSet的区别是LinkedHashSet按照元素插入的顺序进行迭代,即迭代输出的顺序与插入的顺序保持一致

TreeSet

TreeSet 类同时实现了 Set 接口和 NavigableSet 接口。TreeSet是通过二叉树来实现的。

Java集合---List

发表于 2019-03-30 | 分类于 java

如图:

List

List

List是有序的集合,允许使用者控制其插入集合的位置,并能
使用整数索引访问数据

AbstractList

1
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

AbstractList实现List,是一个抽象类。AbstractList提供了List的大部分实现,继承AbstractList,
只强制要求实现get(int index)和size()方法,不过AbstractList中的许多方法都是默认实现,需要覆盖,
例如add(int index,E e)方法默认抛出异常。

AbstractSequentialList

AbstractSequentialList提供了AbstractList的大部分实现,只支持按次序访问。当顺序访问数据(例如使用链表来实现)时,优先使用这个类;
当对于随机访问数据(例如使用数组来实现)时AbstractList优先使用。

Deque

Deque是Queue的子接口,Queue是一种队列形式,而Deque则是双向队列,它支持从两个端点方向检索和插入元素,
因此Deque既可以支持LIFO(后进先出)形式也可以支持FIFO(先进先出)形式.Deque接口是一种比Stack和Vector更为丰富的抽象数据形式,
因为它同时实现了以上两者.

ArrayList

ArrayList是一个数组列表,ArrayList支持存入null.注意ArrayList不是线程同步的,如果要求线程同步,
要使用Vector

注意:ArrayList适合搜索频繁的场景,不适合频繁的插入和删除的场景。

LinkedList

LinkedList是链表集合(在Java中所有的链表都是双向链接的)。

LinkedList适合频繁的插入和删除的场景,不适合搜索频繁的场景

ListIterator

ListIterator继承Iterator,它为List增加Iterator方法,例如add()

Java集合---集合概述

发表于 2019-03-30 | 分类于 java

早期的集合框架

Java最初的版本只为最为常用的数据结构提供了少部分的类。到JavaSE 1.2版本时,
推出了一套功能完整的集合框架。现在我们介绍一下早期的集合类,以及之后替代它们的
类。

早期类或接口 类型 作用 替代类
Enumeration 接口 通过Enumeration中的方法一次获得一个对象集合中的元素,还未被遗弃,在传统的类(如Vector和Properties)中有作用 Iterator
Dictionary 抽象类 用来存储键/值对,作用和Map类相似 Map
Vector 类 可变数组,和ArrayList类似。Java 1.2后被改造为线程同步 Vector
Stack 类 后进先出的队列 Deque
Bitset 类 一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。Java 1.2后重新设计。 Bitset

现在的集合框架

如图:

集合框架中的接口和抽象类

集合框架

List

List

Set

Set

Queue

Queue

Map

Map

log4j详解

发表于 2019-03-28 | 分类于 工具的使用

转载详解log4j2(上) - 从基础到实战

转载详解log4j2(下) - Async/MongoDB/Flume Appender 按日志级别区分文件输出

官方教程

菜鸟话Java---Java序列化

发表于 2019-03-27 | 分类于 java

转载深入理解 Java 序列化

深入理解 Java 序列化

:notebook: 本文已归档到:「blog」

:keyboard: 本文中的示例代码已归档到:「javacore」

简介

  • 序列化(serialize) - 序列化是将对象转换为字节流。
  • 反序列化(deserialize) - 反序列化是将字节流转换为对象。

序列化用途

  • 序列化可以将对象的字节序列持久化——保存在内存、文件、数据库中。
  • 在网络上传送对象的字节序列。
  • RMI(远程方法调用)

:bell: 注意:使用 Java 对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。

序列化和反序列化

Java 通过对象输入输出流来实现序列化和反序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
java.io.ObjectOutputStream 类的 writeObject() 方法可以实现序列化;
java.io.ObjectInputStream 类的 readObject() 方法用于实现反序列化。

序列化和反序列化示例:

public class SerializeDemo01 {
enum Sex {
MALE,
FEMALE
}


static class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
private Integer age = null;
private Sex sex;

public Person() { }

public Person(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}';
}
}

/**
* 序列化
*/
private static void serialize(String filename) throws IOException {
File f = new File(filename); // 定义保存路径
OutputStream out = new FileOutputStream(f); // 文件输出流
ObjectOutputStream oos = new ObjectOutputStream(out); // 对象输出流
oos.writeObject(new Person("Jack", 30, Sex.MALE)); // 保存对象
oos.close();
out.close();
}

/**
* 反序列化
*/
private static void deserialize(String filename) throws IOException, ClassNotFoundException {
File f = new File(filename); // 定义保存路径
InputStream in = new FileInputStream(f); // 文件输入流
ObjectInputStream ois = new ObjectInputStream(in); // 对象输入流
Object obj = ois.readObject(); // 读取对象
ois.close();
in.close();
System.out.println(obj);
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
final String filename = "d:/text.dat";
serialize(filename);
deserialize(filename);
}
}
// Output:
// Person{name='Jack', age=30, sex=MALE}

Serializable 接口

被序列化的类必须属于 Enum、Array 和 Serializable 类型其中的任何一种。

如果不是 Enum、Array 的类,如果需要序列化,必须实现 java.io.Serializable 接口,否则将抛出 NotSerializableException 异常。这是因为:在序列化操作过程中会对类型进行检查,如果不满足序列化类型要求,就会抛出异常。

我们不妨做一个小尝试:将 SerializeDemo01 示例中 Person 类改为如下实现,然后看看运行结果。

1
2
3
4
public class UnSerializeDemo {
static class Person { // 其他内容略 }
// 其他内容略
}

输出:结果就是出现如下异常信息。

1
2
Exception in thread "main" java.io.NotSerializableException:
...

serialVersionUID

请注意 serialVersionUID 字段,你可以在 Java 世界的无数类中看到这个字段。
serialVersionUID 有什么作用,如何使用 serialVersionUID?

serialVersionUID 是 Java 为每个序列化类产生的版本标识。它可以用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID 与发送方发送的 serialVersionUID 不一致,会抛出 InvalidClassException。

如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值。尽管这样,还是建议在每一个序列化的类中显式指定 serialVersionUID 的值。因为不同的 jdk 编译很可能会生成不同的 serialVersionUID 默认值,从而导致在反序列化时抛出 InvalidClassExceptions 异常。
serialVersionUID 字段必须是 static final long 类型。

我们来举个例子:
(1)有一个可序列化类 Person

1
2
3
4
5
6
7
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String address;
// 构造方法、get、set 方法略
}

(2)开发过程中,对 Person 做了修改,增加了一个字段 email,如下:

1
2
3
4
5
6
7
8
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String address;
private String email;
// 构造方法、get、set 方法略
}

由于这个类和老版本不兼容,我们需要修改版本号:
private static final long serialVersionUID = 2L;

再次进行反序列化,则会抛出 InvalidClassException 异常。
综上所述,我们大概可以清楚:serialVersionUID 用于控制序列化版本是否兼容。若我们认为修改的可序列化类是向后兼容的,则不修改 serialVersionUID。

默认序列化机制

如果仅仅只是让某个类实现 Serializable 接口,而没有其它任何处理的话,那么就会使用默认序列化机制。

使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对其父类的字段以及该对象引用的其它对象也进行序列化。同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

注意:这里的父类和引用对象既然要进行序列化,那么它们当然也要满足序列化要求:被序列化的类必须属于 Enum、Array 和 Serializable 类型其中的任何一种。
非默认序列化机制

在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
transient 关键字
当某个字段被声明为 transient 后,默认序列化机制就会忽略该字段。

我们将 SerializeDemo01 示例中的内部类 Person 的 age 字段声明为 transient,如下所示:

1
2
3
4
5
6
7
8
9
public class SerializeDemo02 {
static class Person implements Serializable {
transient private Integer age = null;
// 其他内容略
}
// 其他内容略
}
// Output:
// name: Jack, age: null, sex: MALE

从输出结果可以看出,age 字段没有被序列化。

Externalizable 接口

无论是使用 transient 关键字,还是使用 writeObject() 和 readObject() 方法,其实都是基于 Serializable 接口的序列化。
JDK 中提供了另一个序列化接口–Externalizable。

可序列化类实现 Externalizable 接口之后,基于 Serializable 接口的默认序列化机制就会失效。
我们来基于 SerializeDemo02 再次做一些改动,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ExternalizeDemo01 {
static class Person implements Externalizable {
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();
}

@Override
public void writeExternal(ObjectOutput out) throws IOException { }

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { }
}
// 其他内容略
}
// Output:
// call Person()
// name: null, age: null, sex: null

从该结果,一方面可以看出 Person 对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了 Person 类的无参构造方法。

Externalizable 继承于 Serializable,它增添了两个方法:writeExternal() 与 readExternal()。这两个方法在序列化和反序列化过程中会被自动调用,以便执行一些特殊操作。当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于 writeExternal() 与 readExternal() 方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。

另外,若使用 Externalizable 进行序列化,当读取对象时,会调用被序列化类的无参构造方法去创建一个新的对象;然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中 Person 类的无参构造方法会被调用。由于这个原因,实现 Externalizable 接口的类必须要提供一个无参的构造方法,且它的访问权限为 public。
对上述 Person 类作进一步的修改,使其能够对 name 与 age 字段进行序列化,但要忽略掉 gender 字段,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ExternalizeDemo02 {
static class Person implements Externalizable {
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();
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
}
// 其他内容略
}
// Output:
// call Person()
// name: Jack, age: 30, sex: null

Externalizable 接口的替代方法

实现 Externalizable 接口可以控制序列化和反序列化的细节。它有一个替代方法:实现 Serializable 接口,并添加 writeObject(ObjectOutputStream out) 与 readObject(ObjectInputStream in) 方法。序列化和反序列化过程中会自动回调这两个方法。
示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SerializeDemo03 {
static 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();
}
// 其他内容略
}
// 其他内容略
}
// Output:
// name: Jack, age: 30, sex: MALE

在 writeObject() 方法中会先调用 ObjectOutputStream 中的 defaultWriteObject() 方法,该方法会执行默认的序列化机制,如上节所述,此时会忽略掉 age 字段。然后再调用 writeInt() 方法显示地将 age 字段写入到 ObjectOutputStream 中。readObject() 的作用则是针对对象的读取,其原理与 writeObject() 方法相同。

注意:writeObject() 与 readObject() 都是 private 方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见 ObjectOutputStream 中的 writeSerialData 方法,以及 ObjectInputStream 中的 readSerialData 方法。

readResolve() 方法

当我们使用 Singleton 模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第 2 节使用的 Person 类进行修改,使其实现 Singleton 模式,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class SerializeDemo04 {

enum Sex {
MALE, FEMALE
}

static class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
transient private Integer age = null;
private Sex sex;
static final Person instatnce = new Person("Tom", 31, Sex.MALE);

private Person() {
System.out.println("call Person()");
}

private Person(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public static Person getInstance() {
return instatnce;
}

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

public String toString() {
return "name: " + this.name + ", age: " + this.age + ", sex: " + this.sex;
}
}

/**
* 序列化
*/
private static void serialize(String filename) throws IOException {
File f = new File(filename); // 定义保存路径
OutputStream out = new FileOutputStream(f); // 文件输出流
ObjectOutputStream oos = new ObjectOutputStream(out); // 对象输出流
oos.writeObject(new Person("Jack", 30, Sex.MALE)); // 保存对象
oos.close();
out.close();
}

/**
* 反序列化
*/
private static void deserialize(String filename) throws IOException, ClassNotFoundException {
File f = new File(filename); // 定义保存路径
InputStream in = new FileInputStream(f); // 文件输入流
ObjectInputStream ois = new ObjectInputStream(in); // 对象输入流
Object obj = ois.readObject(); // 读取对象
ois.close();
in.close();
System.out.println(obj);
System.out.println(obj == Person.getInstance());
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
final String filename = "d:/text.dat";
serialize(filename);
deserialize(filename);
}
}
// Output:
// name: Jack, age: null, sex: MALE
// false

值得注意的是,从文件中获取的 Person 对象与 Person 类中的单例对象并不相等。为了能在单例类中仍然保持序列的特性,可以使用 readResolve() 方法。在该方法中直接返回 Person 的单例对象。我们在 SerializeDemo04 示例的基础上添加一个 readObject 方法, 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SerializeDemo05 {
// 其他内容略

static class Person implements Serializable {

// 添加此方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}
// 其他内容略
}

// 其他内容略
}
// Output:
// name: Tom, age: 31, sex: MALE

序列化工具

Java 官方的序列化存在许多问题,因此,很多人更愿意使用优秀的第三方序列化工具来替代 Java 自身的序列化机制。

Java 官方的序列化主要体现在以下方面:

  • Java 官方的序列化性能不高,序列化后的数据相对于一些优秀的序列化的工具,还是要大不少,这大大影响存储和传输的效率。
  • Java 官方的序列化一定需要实现 Serializable 接口。
  • Java 官方的序列化需要关注 serialVersionUID。
  • Java 官方的序列无法跨语言使用。

当然我们还有更加优秀的一些序列化和反序列化的工具,根据不同的使用场景可以自行选择!
thrift、protobuf - 适用于对性能敏感,对开发体验要求不高的内部系统。
hessian - 适用于对开发体验敏感,性能有要求的内外部系统。
jackson、gson、fastjson - 适用于对序列化后的数据要求有良好的可读性(转为 json 、xml 形式)。
小结

参考资料:

  • Java 编程思想
  • JAVA 核心技术(卷 1)
  • www.hollischuang.com/archives/11…
  • www.codenuclear.com/serializati…
  • www.blogjava.net/jiangshachi…
  • Java 序列化的高级认识
  • agapple.iteye.com/blog/859052

菜鸟话Java---Java泛型

发表于 2019-03-27 | 分类于 java

定义泛型类

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

public class Pair<T> {//泛型类可以有多个类型变量,例如 Pair<T,U>

private T first;//可以用具体的类型替换

private T second;

public Pair(){
first = null;
second = null;
}

public Pair(T first, T second) {
this.first = first;
this.second = second;
}

public T getFirst() {
return first;
}

public void setFirst(T first) {
this.first = first;
}

public T getSecond() {
return second;
}

public void setSecond(T second) {
this.second = second;
}
}


public static void main(String[] args) {
Pair<String> pair = new Pair<>();//使用 String 来替换泛型类型,Pair中的方法就变成
pair.setFirst(String);
pair.setSecond(String);
....
}

在Java中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。
T(需要时好i可以用临近的字母U和S)表示”任意类型”

泛型方法

定义泛型方法

1
2
3
4
5
6
7
8
9
10
public class Util {
//获取中间值
public static <T>T getMiddle(T... t){
...
}

public <T>void get(T t){

}
}

使用泛型方法

1
2
3
String information = Util.<String>getMiddle("12345","qww","sss");
或
String information = Util.getMiddle("12345","qww","sss");//一般编译器可以推断,所以可以省略

注意:例如double d = Util.getMiddle(1.0,1234,0),编译器会先找到Double和Integer的超类,但是
它们的超类和接口,都是泛型类型。这种情况下,会报错,唯一的解决办法就是把所有参数写成double

类型变量的限定

语法:

1
<T extends 类型>

示例:

1
2
3
public class Util<T extends Comparable>{

}

注意:在Java继承中,可以根据需要拥有多个接口类型,但限定中至多有一个类。如果用
类限定,它必须是限定列表中的第一个。

类型擦除

Java虚拟机中没有泛型对象,所有的对象都属于普通类。无论何时定义一个泛型类型,在Java虚拟机
中都会擦除类型变量,并替换为限定类型(无限定的变量用Object)

例如,Pair<T>的原始类型为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Pair<Object> {//泛型类可以有多个类型变量,例如 Pair<T,U>

private Object first;//可以用具体的类型替换

private Object second;

public Pair(){
first = null;
second = null;
}

public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}

public Object getFirst() {
return first;
}

public void setFirst(Object first) {
this.first = first;
}

public Object getSecond() {
return second;
}

public void setSecond(Object second) {
this.second = second;
}
}

由于T是一个无限定的变量,所以直接用Object替代。

在程序中可以包含不同类型的Pair,例如,Pair<String>或Pair<LocalDate>.而
擦除类型后就变成原始的Pair类型。

如果有限定,例如:

1
2
3
public class Util<T extends Serializable&Comparable>{

}

原始类型会用第一个限定类型来替代。上例中就为Serializable,编译器只在必要的时候
插入Comparable强制类型转换。为了提高效率应该将标记接口(即没有方法的接口,比如
Serializable)放在后面

有返回值的泛型方法

当调用有返回值的泛型方法时,编译器会插入强制类型转换,例如:

1
2
Pair<String> pair = ....;
String a = pair.getFirst();

转换为:

1
2
Pair<String> pair = ....;
String a = (String)pair.getFirst();

桥方法

1
2
3
4
5
6
public class Test extends Pair<LocalDate> {

public void setSecond(LocalDate second) {
super.setSecond(second);
}
}

由上面的示例,当你继承Pair<LocalDate>时,由于类型擦除,
Pair<LocalDate>中只存在setSecond(Object sencod)方法,当我们
想通过Pair<LocalDate> pair = new Test()来调用setSencod时,就
与多态发生冲突。

为了解决这个问题,编译器会在Test中生成桥方法,例如

1
2
3
public void setSecond(Object second){
setSecond((LocalDate)second)
}

如果我们覆盖getSecond()方法,则存在两个getSecond()方法

1
2
LocalDate getSecond()
Object getSecond()//桥方法,用来覆盖父类的方法

注意:具有相同的参数类型的两个方法是不合法的,不能写这样的Java代码。
但是,在虚拟机中,用参数类型和返回值类型来确定一个方法。因此,编译器可
产生两个仅返回值类型不同的方法

Java泛型转换的总结

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都用它们的限定类型替代
  • 桥方法被合成保持多态
  • 为保持类型的安全性,必要时插入强制类型转换

泛型的约束

不能用基本类型来实例化类型参数

例如Pair<double>是不被允许的,不过可以使用基本类型的包装类

运行时类型查询只适用于原始类型

例如:

1
2
3
4
5
6
7
a instanceof Pair<String> //编译器报错

a instanceof Pair<T>//编译器报错

Pair<String> s = ...;
Pair<Integer> i = ...;
if(s.getClass() == i.getClass())//返回true,因为两次调用都会返回Pair.class

不能创建参数化类型数组

例如

1
2
Pair<String>[] p = new Pair<String>[10];//编译器报错
`

不过可以声明通配符(后面介绍)类型的数组,然后进行类型转换

1
Pair<String>[] p = (Pair<String>[])new Pair<?>[10];//结果不安全,由于数组储存只会检查擦除后的类型

注意:对于可变参数(本质是数组),虽然违法了上面的规则,但是你只会收到一个警告

不能直接实例化类型变量

例如:

1
T t = new T();//编译器报错

实例化类型变量的方法

1.方法1,使用反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class C<T> {

T value;

public C(T value){
this.value = value;
}

public static <T> C<T> make(Class<T> cl){
try {
return new C<>(cl.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}

}

//调用
C<String> c1 = C.make(String.class);
System.out.println(c1.value.getClass());

//输出为
class java.lang.String

2.方法2,使用构造器表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class C<T> {

T value;

public C(T value){
this.value = value;
}


public static <T> C<T> make(Supplier<T> supplier){
return new C<>(supplier.get());
}

}

//调用
C<String> c1 = C.make(String::new);
System.out.println(c1.value.getClass());

//输出为
class java.lang.String

### 不能直接构造泛型数组

### 泛型类的静态变量和静态方法的类型变量无效

```java
public class Pair<T> {

private static T first;//报错

public static void setFirst(T first) {//报错
this.first = first;
}
...

不能抛出或捕获泛型类的实例

可以消除对受查异常的检查

注意擦除后的冲突

泛型类型的继承规则

注意:无论T和S是什么关系(比如继承),通常,C<T>和C<S>没有什么联系

永远可以将一个参数化类型转换为一个原始类型,例如,C<String>是原始类型C的子类型

通配符

1
2
3
List<A> list = new ArrayList();
list.add(new A());
list.add(new B());//尽管B是A的子类,但是不能这样做

为了解决这个问题,就可以使用通配符,例如

1
2
3
List<? extends A) list = new ArrayList();
list.add(new A());
list.add(new B());

字符编码详解

发表于 2019-03-26 | 分类于 基础知识

ASCII

ASCII是美国(国家)信息交换标准(代)码,一种使用7个或8个二进制位进行编码的方案,最多可以给256(2^80)个字符
(包括字母、数字、标点符号、控制字符及其他符号)分配(或指定)数值。基本的 ASCII 字符集共有 128 个字符,其中有
96 个可打印字符,包括常用的字母、数字、标点符号等,另外还有 32 个控制字符。

文件中每一个字都是美标形象码或空格码,这类文件称为“美标文本文件”,或略为“文本文件”,通常可在不同电脑系统间直接交换。
文件中含有控制码或非美标码的文件,通常不能在不同电脑系统间直接交换。这类文件有一个通称,叫“二进制文件”

ANSI

为了扩充ASCII编码,以用于显示本国的语言,不同的国家和地区制定了不同的标准,由此产生了GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码,又称为MBCS(Muilti-Bytes Charecter Set,多字节字符集)。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码,所以在中文 windows下要转码成GB2312,GBK(gb2312的扩展)只需要把文本保存为ANSI 编码即可。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。一个很大的缺点是,同一个编码值,在不同的编码体系里代表着不同的字。这样就容易造成混乱

GB2312

GB2312是ANSI编码里的一种,对ANSI编码最初始的ASCII编码进行扩充,为了满足国内在计算机中使用汉字的需要,中国国家标准总局发布了一系列的汉字字符集国家标准编码,统称为GB码,或国标码。其中最有影响的是于1980年发布的《信息交换用汉字编码字符集 基本集》,标准号为GB 2312-1980,因其使用非常普遍,也常被通称为国标码。GB2312编码通行于我国内地;新加坡等地也采用此编码。几乎所有的中文系统和国际化的软件都支持GB2312。
GB2312是一个简体中文字符集,由6763个常用汉字和682个全角的非汉字字符组成

GBK

GB 2312的出现,基本满足了汉字的计算机处理需要,但对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现
GBK共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003个,图形符号883个。GBK编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。GB2312码是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集——基本集》,1980年由国家标准总局发布。基本集共收入汉字6763个和非汉字图形字符682个,通行于中国大陆。新加坡等地也使用此编码。GBK是对GB2312-80的扩展.

Big5

在台湾、香港与澳门地区,使用的是繁体中文字符集。而1980年发布的GB2312面向简体中文字符集,并不支持繁体汉字。在这些使用繁体中文字符集的地区,一度出现过很多不同厂商提出的字符集编码,这些编码彼此互不兼容,造成了信息交流的困难。为统一繁体字符集编码,1984年,台湾五大厂商宏碁、神通、佳佳、零壹以及大众一同制定了一种繁体中文编码方案,因其来源被称为五大码,英文写作Big5,后来按英文翻译回汉字后,普遍被称为大五码。大五码是一种繁体中文汉字字符集,其中繁体汉字13053个,808个标点符号、希腊字母及特殊符号。

unicode

为什么电子邮件和网页都经常会出现乱码,就是因为信息的提供者可能是日文的ANSI编码体系和信息的读取者可能是中文的编码体系,他们对同一个二进制编码值进行显示,采用了不同的编码,导致乱码。这个问题促使了unicode码的诞生。
如果有一种编码,将世界上所有的符号都纳入其中,无论是英文、日文、还是中文等,大家都使用这个编码表,就不会出现编码不匹配现象。每个符号对应一个唯一的编码,乱码问题就不存在了。这就是Unicode编码。

unicode,中文叫万国码,统一码,是统一码联盟为了世界上大多数文字系统进行整理和编码。
和unicode类似,iso组织也在做同样的事情,iso开展了 ISO/IEC 10646项目,名字叫“ Universal Multiple-Octet Coded Character Set”,简称UCS。
后来,双方意识到时间上不需要2套通用的字符集,所以双方开始进行整合,到unicode2.0时,unicode的编码和ucs的编码都基本一致。
但是又略有不同。

Unicode深入人心,且UTF-8(Unicode中的一种)大行其道,UCS编码基本被等同于UTF-16,UTF-32了,所以目前UCS基本谈出人们的视野中。

UTF-8

Unicode固然统一了编码方式,但是它的效率不高,比如UCS-4(Unicode的标准之一)规定用4个字节存储一个符号,那么每个英文字母前都必然有三个字节是0,这对存储和传输来说都很耗资源。
为了提高Unicode的编码效率,于是就出现了UTF-8编码。UTF-8可以根据不同的符号自动选择编码的长短。比如英文字母可以只用1个字节就够了。

UTF-16

UTF-16是Unicode的其中一个使用方式,UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节) 储存,但UTF-16却无法兼容于ASCII编码。

Base64

有的电子邮件系统(比如国外信箱)不支持非英文字母(比如汉字)传输,这是历史原因造成的(认为只有美国会使用电子邮件?)。因为一个英文字母使用ASCII编码来存储,占存储器的1个字节(8位),实际上只用了7位2进制来存储,第一位并没有使用,设置为0,所以,这样的系统认为凡是第一位是1的字节都是错误的。而有的编码方案(比如GB2312)不但使用多个字节编码一个字符,并且第一位经常是1,于是邮件系统就把1换成0,这样收到邮件的人就会发现邮件乱码。

为了能让邮件系统正常的收发信件,就需要把由其他编码存储的符号转换成ASCII码来传输。比如,在一端发送GB2312编码->根据Base64规则->转换成ASCII码,接收端收到ASCII码->根据Base64规则->还原到GB2312编码。

BMP

UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外

更详细的可以看

  • 网页编码就是那点事
  • 计算机程序的思维逻辑 (6) - 如何从乱码中恢复 (上)
  • 计算机程序的思维逻辑 (6) - 如何从乱码中恢复 (下)
  • 计算机程序的思维逻辑 (8) - char的真正含义

参考:

  • http://www.fmddlmyy.cn/text6.html
  • https://zhidao.baidu.com/question/875623864035336132.html
  • https://baike.baidu.com/item/字符编码/8446880?fr=aladdin
  • https://baike.baidu.com/item/UTF-16/9032026?fr=aladdin

源码解析---CharSequence

发表于 2019-03-26 | 分类于 java源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* CharSequence是 char 值的可读序列。此接口提供对许多不同类型的 char 序列的统一,只读访问。
* char 值表示 Basic 多语言平面(BMP)中的字符或代理项。
* 此接口没有实现equals和 hashCode()方法。因此,比较实现CharSequence 的两个对象的结果通常是不可控的。
* 每个对象可以由不同的类实现,并且不能保证每个类能够测试其实例与另一个实例的相等性。
* 因此,使用任意 CharSequence 实例作为集合中的元素或映射中的键是不合适的
*/

public interface CharSequence {

/**
*返回字符序列的长度
*/
int length();

/**
*返回指定位置的字符,0<=index<=length-1
*/
char charAt(int index);

/**
*返回[start,end)位置的字符序列,长度为end - start,如果start == end,则返回空的字符
*
*/
CharSequence subSequence(int start, int end);

/**
*和Object的toString()方法一样
*/
public String toString();

/**
*Java8中的新特性,之后会介绍
*/
public default IntStream chars() {
class CharIterator implements PrimitiveIterator.OfInt {
int cur = 0;

public boolean hasNext() {
return cur < length();
}

public int nextInt() {
if (hasNext()) {
return charAt(cur++);
} else {
throw new NoSuchElementException();
}
}

@Override
public void forEachRemaining(IntConsumer block) {
for (; cur < length(); cur++) {
block.accept(charAt(cur));
}
}
}

return StreamSupport.intStream(() ->
Spliterators.spliterator(
new CharIterator(),
length(),
Spliterator.ORDERED),
Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
false);
}

/**
*Java8中的新特性,之后会介绍
*/
public default IntStream codePoints() {
class CodePointIterator implements PrimitiveIterator.OfInt {
int cur = 0;

@Override
public void forEachRemaining(IntConsumer block) {
final int length = length();
int i = cur;
try {
while (i < length) {
char c1 = charAt(i++);
if (!Character.isHighSurrogate(c1) || i >= length) {
block.accept(c1);
} else {
char c2 = charAt(i);
if (Character.isLowSurrogate(c2)) {
i++;
block.accept(Character.toCodePoint(c1, c2));
} else {
block.accept(c1);
}
}
}
} finally {
cur = i;
}
}

public boolean hasNext() {
return cur < length();
}

public int nextInt() {
final int length = length();

if (cur >= length) {
throw new NoSuchElementException();
}
char c1 = charAt(cur++);
if (Character.isHighSurrogate(c1) && cur < length) {
char c2 = charAt(cur);
if (Character.isLowSurrogate(c2)) {
cur++;
return Character.toCodePoint(c1, c2);
}
}
return c1;
}
}

return StreamSupport.intStream(() ->
Spliterators.spliteratorUnknownSize(
new CodePointIterator(),
Spliterator.ORDERED),
Spliterator.ORDERED,
false);
}
}

Character源码分析

发表于 2019-03-26 | 分类于 java源码分析

转载Java源码浅析,Character(1)

转载Java源码浅析,Character(2)

转载Java源码浅析,Character(3)

转载Java源码浅析,Character(4)

菜鸟话Java---java断言

发表于 2019-03-25 | 分类于 java

断言

assert关键字语法很简单,有两种用法:

1、assert <boolean表达式>
如果<boolean表达式>为true,则程序继续执行。
如果为false,则程序抛出AssertionError,并终止执行。

2、assert <boolean表达式> : <错误信息表达式>
如果<boolean表达式>为true,则程序继续执行。
如果为false,则程序抛出java.lang.AssertionError,并输入<错误信息表达式>。

现在断言不常用

1…345…7
lichukuan

lichukuan

62 博客
12 分类
© 2019 lichukuan
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4