必知必会之 Java
必知必会之 Java
目录
不定期更新中……
- List
- [ArrayList 的底层实现](#ArrayList 的底层实现)
- [ArrayList 如何扩容?](#ArrayList 如何扩容?)
- Map
- [HashMap 的底层实现](#HashMap 的底层实现)
- [JDK 1.8 中 HashMap 为什么要引入红黑树?](#JDK 1.8 中 HashMap 为什么要引入红黑树?)
- [HashMap 什么情况使用链表?什么情况会使用红黑树?](#HashMap 什么情况使用链表?什么情况会使用红黑树?)
[IO 流](#IO 流)
- [IO 流的种类](#IO 流的种类)
- [常见的 IO 流](#常见的 IO 流)
- BIO、NIO、AIO
- [NIO 的组成](#NIO 的组成)
- 零拷贝
- [将 ASCII 码转成字符](#将 ASCII 码转成字符)
- 获取 Class 对象的方法
- 什么是字节码?字节码有哪些使用场景?
- 什么是字节码增强技术?字节码增强有哪些使用场景?
基础知识
数据计量单位
8bit(位)=1Byte(字节)
1024Byte(字节)=1KB
1024KB=1MB
1024MB=1GB
1024GB=1TB
1024TB=PB
1024PB=1EB
1024EB=1ZB
1024ZB=1YB
1024YB=1BB
面向对象三大特性
封装:隐藏不想对外暴露的信息,提高安全性;抽取公共代码,提高可复用性。
继承:继承为类的扩展提供了一种方式。有利于修改公共属性或方法,父类修改,所有子类无需重复修改。
多态:类的多态体现在重写和重载,重写通过继承来实现,重载通过相同方法的不同参数来实现。
基础数据类型
数据类型 | 位数 | 取值范围 | 可转类型 |
---|---|---|---|
byte | 8 | -128 ~ 127(-2^7 ~ 2^7-1) | |
short | 16 | -32,768 ~ 32,767(-2^15 ~ 2^15-1) | int、long、float、double |
int | 32 | -2,147,483,648 ~ 2,147,483,647(-2^31 ~ 2^31-1) | long、float、double |
long | 64 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807(-2^63 ~ 2^63-1) | int、long、float、double |
float | 32 | double | |
double | 64 | ||
char | 16 | \u0000 ~ \uffff(65 ~ 535) | int、long、float、double |
boolean | 1 | true、false |
注释格式
单行注释:
// this is a comment
多行注释:
/* this is a comment */
文档注释:
/**
* this is a comment
*/
访问修饰符
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
private | √ | × | × | × |
default | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
运算符
算数运算符
操作符 | 描述 | 例子 |
---|---|---|
+ | 加法 - 相加运算符两侧的值 | A + B = 30 |
- | 减法 - 左操作数减去右操作数 | A – B = -10 |
* | 乘法 - 相乘操作符两侧的值 | A * B = 200 |
/ | 除法 - 左操作数除以右操作数 | B / A = 2 |
% | 取余 - 左操作数除以右操作数的余数 | B % A = 0 |
++ | 自增: 操作数的值增加1 | B++ = 21 或 ++B = 21 |
-- | 自减: 操作数的值减少1 | B-- == 19 或 --B == 19 |
关系运算符
运算符 | 描述 | 例子 |
---|---|---|
== | 检查如果两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= | 检查如果两个操作数的值是否相等,如果值不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是那么条件为真。 | (A > B) 为假。 |
< | 检查左操作数的值是否小于右操作数的值,如果是那么条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。 | (A >= B) 为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。 | (A <= B) 为真。 |
位运算符
操作符 | 描述 | 例子 |
---|---|---|
& | 与。如果相对应位都是 1,则结果为 1,否则为 0 | (A & B) 得到12,即 0000 1100 |
| | 或。如果相对应位都是 0,则结果为 0,否则为 1 | (A | B) 得到 61,即 0011 1101 |
^ | 异或。如果相对应位值相同,则结果为 0,否则为1 | (A ^ B) 得到 49,即 0011 0001 |
~ | 取反。翻转操作数的每一位,即 0 变成 1,1 变成 0。 | (~A) 得到 -61,即 1100 0011 |
<< | 左移。左操作数按位左移右操作数指定的位数。 | A << 2 得到 240,即 1111 0000 |
>> | 右移。左操作数按位右移右操作数指定的位数。 | A >> 2 得到 15,即 1111 |
>>> | 无符号右移。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 | A>>>2 得到 15,即 0000 1111 |
逻辑运算符
操作符 | 描述 | 例子 |
---|---|---|
&& | 逻辑与,也称短路与。当且仅当两个操作数都为真,条件才为真。若第一个操作数为假,则第二个操作数不再判断。 | (A && B) 为假。 |
|| | 逻辑或,也称短路或。如果任何两个操作数任何一个为真,条件为真。若第一个操作数为假,则第二个操作数不再判断。 | (A || B) 为真。 |
! | 逻辑非。用来反转操作数的逻辑状态。如果条件为 true,则使用逻辑非运算符将得到 false。 | !(A && B) 为真。 |
赋值运算符
操作符 | 描述 | 例子 |
---|---|---|
= | 简单的赋值运算符,将右操作数的值赋给左侧操作数 | C = A + B 将把 A + B 得到的值赋给 C |
+= | 加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数 | C += A 等价于 C = C + A |
-= | 减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数 | C -= A 等价于 C = C - A |
*= | 乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数 | C *= A 等价于 C = C * A |
/= | 除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数 | C /= A,C 与 A 同类型时等价于 C = C / A |
%= | 取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数 | C %= A 等价于 C = C % A |
<<= | 左移位赋值运算符 | C <<= 2 等价于 C = C << 2 |
>>= | 右移位赋值运算符 | C >>= 2 等价于 C = C >> 2 |
&= | 按位与赋值运算符 | C &= 2 等价于 C = C & 2 |
^= | 按位异或赋值操作符 | C ^ = 2 等价于 C = C ^ 2 |
|= | 按位或赋值操作符 | C | = 2 等价于 C = C | 2 |
三目表达式
// 若 a == b 成立,返回 true,否则返回 false
boolean flag = (a == b) ? true : false
运算符优先级
所谓“好记性不如烂笔头”。实际开发中,尽量使用括号来明确优先级,提高代码可读性,而非使用复杂的运算符复合运算。
如:((x++) && (y + 1) || z == 0)
优先级 | 运算符 | 结合性 |
---|---|---|
1 | ()、[]、{} | 从左向右 |
2 | !、+、-、~、++、-- | 从右向左 |
3 | *、/、% | 从左向右 |
4 | +、- | 从左向右 |
5 | «、»、>>> | 从左向右 |
6 | <、<=、>、>=、instanceof | 从左向右 |
7 | ==、!= | 从左向右 |
8 | & | 从左向右 |
9 | ^ | 从左向右 |
10 | | | 从左向右 |
11 | && | 从左向右 |
12 | || | 从左向右 |
13 | ?: | 从右向左 |
14 | =、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>= | 从右向左 |
拷贝
什么是浅拷贝?
被复制对象的所有变量值与原对象相同,但引用变量仍然指向原来的对象。即浅拷贝只复制对象本身,而不复制对象中引用的对象。
示例:
Teacher teacher = new Teacher();
teacher.setName("赵大");
teacher.setAge(42);
Student student1 = new Student();
student1.setName("张三");
student1.setAge(21);
student1.setTeacher(teacher);
Student student2 = (Student) student1.clone();
System.out.println("李四");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------");
// 修改老师名称
teacher.setName("John");
// 两个学生的老师均发生变化
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
什么是深拷贝?
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
深拷贝的方法包括:
重写 clone() 方法
public class Student implements Cloneable { private String name;
private Integer age;
private Teacher teacher; // 省略 get/set 方法 @Override
protected Object clone() throws CloneNotSupportedException {
Student3 student = (Student3) super.clone();
// 复制一个新的 Teacher 对象实例,并设置到新的 student 对象实例中
student.setTeacher((Teacher2) student.getTeacher().clone());
return student;
}
}
使用序列化实现
public class Teacher implements Serializable {
private String name;
private int age; // 省略 get/set 方法
} class Student implements Serializable {
private String name;
private int age;
private Teacher3 teacher; // 省略 get/set 方法 public Object deepClone() throws Exception {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject();
}
}
重写与重载
什么是重写?
重写是指子类对父类允许访问的方法进行重新编写,返回值和形参都不能改变。即方法入参出参不变,实现逻辑重写。
示例:
public class OverrideParent {
public void m1(String var) {
System.out.println(var);
}
}
public class OverrideChild extends OverrideParent {
/**
* 重写父类的 m1() 方法
*/
@Override
public void m1(String var) {
System.out.println(var);
}
/**
* 子类的 m1() 方法:与父类 m1() 方法的形参不同
*/
public void m1(int var) {
System.out.println(var);
}
/**
* 子类的 m1() 方法:与父类 m1() 方法的形参、返回值不同
*/
public String m1() {
System.out.println("var");
return "var";
}
}
什么是重载?
重载是指一个类中存在多个同名方法,且方法的形参不同。即方法名称相同、形参不同。
示例:
public class OverloadClass {
public void m1() {
System.out.println("key");
}
public void m1(String key) {
System.out.println(key);
}
public void m1(String key, Integer value) {
System.out.println(key);
}
}
重写与重载的区别
- 重写要求方法名、入参、返回值相同,重载只同名方法的入参不同(类型、个数、顺序至少有一个不同)。
- 重写要求子类不能缩小父类方法的访问权限,重载与访问权限无关。
- 重写要求子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常),重载与异常范围无关。
- 重写是子类对父类方法的覆盖行为,重载是一个类的多态性。
- 重写方法不能被定义为 final,重载方法可以被定义为 final。
类加载机制
什么是双亲委派?
在类加载过程中,子类会先去父类查找,如果找到,则从父类缓存加载。如果没找到,再由父类指派子类进行加载。
双亲委派机制主要出于安全来考虑。比如自定义 java.lang.String,如果不先去父类查找,相当于 Bootstrap 加载器的 java.lang.String 被篡改了。
如何打破双亲委派?
重写 loadClass() 方法
JDK 1.2 之前,自定义 ClassLoader 都必须重写 loadClass()
ThreadContextClassLoader 可以实现基础类调用实现类代码,通过 thread.setContextClassLoader 指定
热启动,热部署
OSGI、Tomcat 都有自己的模块指定 Classloader(可以加载同一类库的不同版本)
Java 内存模型
缓存一致性协议
现代 CPU 的数据一致性实现 = 缓存锁(MESI 等) + 总线锁。缓存一致性协议一般是指缓存锁层面的协议,目前缓存一致性协议的实现有很多种,比较常见的就是 Intel 所使用 MESI 协议。
MESI 协议定义了四种状态,分别是 Modified、Exclusive、Shared 和 Invalid。
- Modified 状态:该Cache line有效,数据被修改且未同步到内存,数据和内存数据不一致,数据只存在于本 Cache 中。
- Exclusive 状态:该Cache line有效,数据由单 CPU 独占,数据和内存数据一致,数据只存在于本 Cache 中。
- Shared 状态:该Cache line有效,数据由所有 CPU 共享,数据和内存数据一致,数据存在于所有 Cache 中。
- Invalid 状态:该Cache line无效。
缓存行
读取缓存以 Cache Line 为基本单位,目前 64 bytes。
位于同一缓存行的两个不同数据,被两个不同 CPU 锁定,产生互相影响的伪共享问题,使用缓存行的对齐能够有效解决伪共享问题,提高处理效率。
缓存行的对齐
//一个缓存行64个字节,设置56个的占位符,令要插入的数据单独占用一行缓存行
public static class Padding {
public volatile long p1,p2,p3,p4,p5,p6,p7;
}
public static class T extends Padding {
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(() -> {
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
t1.start();
t2.start();
t1.join();
t1.join();
}
- 使用缓存行对齐的开源软件:Disruptor(号称单机效率最高的队列)
合并写
如果 CPU 需要访问的地址 hash 之后并不在缓存行(cache line)中,那么缓存中对应位置的缓存行(cache line)会失效,以便让新的值可以取代该位置的现有值。例如,如果我们有两个地址,通过 hash 算法 hash 到同一缓存行,那么新的值会覆盖老的值。
当 CPU 执行存储指令(store)时,它会尝试将数据写到离 CPU 最近的 L1 缓存。如果这时出现缓存失效,CPU 会访问下一级缓存。这时无论是英特尔还是许多其他厂商的 CPU 都会使用被称为“合并写(write combining)”的技术。
当请求 L2 缓存行的所有权的时候,最典型的是将处理器的 store buffers 中某一项写入内存的期间, 在缓存子系统(cache sub-system)准备好接收、处理的数据的期间,CPU 可以继续处理其他指令。当数据不在任何缓存层中缓存时,将获得最大的优势。
当连串的写操作需要修改相同的缓存行时,会变得非常有趣。在修改提交到 L2 缓存之前,这连串的写操作会首先合并到缓冲区(buffer)。 这些 64 字节的缓冲(buffers )维护在一个 64 位的区域中,每一个字节(byte)对应一个位(bit),当缓冲区被传输到外缓存后,标志缓存是否有效。随后,硬件在读取缓存之前会先读取缓冲区。
如果我们可以在缓冲区被传输到外缓存之前能够填补这些缓冲区(buffers ),那么我们将大大提高传输总线的效率。由于这些缓冲区的数量是有限的,并且它们根据 CPU 的型号有所不同。例如在 Intel CPU,你只能保证在同一时间拿到 4 个。这意味着,在一个循环中,你不应该同时写超过 4 个截然不同的内存位置,否则你讲不能从合并写(write combining)的中受益。
Java 内存模型包括哪些东西?
程序计数器、方法区、本地方法栈、虚拟机方法栈、堆。
Java 内存模型中,哪些对象是线程私有的?哪些对象是线程公有的?
程序计数器、本地方法栈、虚拟机方法栈是线程私有的,方法区、堆是线程公有的。
如何保证特定情况下不乱序
硬件层面:使用内存屏障
- sfence: store| 在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
- lfence:load | 在lfence指令前的读操作当必须在lfence指令后的读操作前完成。
- mfence:mix | 在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。
原子指令,如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序
JVM层面:使用 JSR133 规范
LoadLoad屏障:
对于这样的语句 Load1; LoadLoad; Load2, 在 Load2 及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。
StoreStore屏障:
对于这样的语句 Store1; StoreStore; Store2,在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。
LoadStore屏障:
对于这样的语句 Load1; LoadStore; Store2,在 Store2 及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕。
StoreLoad屏障:
对于这样的语句 Store1; StoreLoad; Load2,在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。
java 八大原子操作
最新的 JSR-133 已经放弃了这种描述,但 JMM 没有变化。
lock:主内存,标识变量为线程独占
unlock:主内存,解锁线程独占变量
read:主内存,读取内容到工作内存
write:主内存,写变量值
load:工作内存,read 后的值放入线程本地变量副本
use:工作内存,传值给执行引擎
assign:工作内存,执行引擎结果赋值给线程本地变量
store:工作内存,存值到主内存给 write 备用
集合
List
ArrayList 的底层实现
ArrayList 是基于数组实现的,是一个动态数组,其容量能自动增长,类似于 C 语言中的动态申请内存,动态增长内存。
ArrayList 不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用 Collections.synchronizedList(List l) 函数返回一个线程安全的 ArrayList 类,也可以使用并发包下的 CopyOnWriteArrayList 类。
ArrayList 如何扩容?
数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。
当我们可预知要保存的元素的多少时,要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用 ensureCapacity 方法来手动增加 ArrayList 实例的容量。
Map
HashMap 的底层实现
JDK 1.8 之前使用数组 + 单链表实现,JDK 1.8 以后使用数组 + 单链表/红黑树实现。
JDK 1.8 中 HashMap 为什么要引入红黑树?
当 HashMap 中出现较多哈希冲突时,链表有可能会变得非常长,而链表是从链表的 head 或者 tail 查询的,效率会随着长度的增长而降低。引入红黑树就是为了解决链表过长带来的查询效率问题。红黑树的树形结构使原本查询链表的时间复杂度 O(n) 降到了 O(logn)。
HashMap 什么情况使用链表?什么情况会使用红黑树?
若桶中链表元素超过 8 时,会自动转化成红黑树;若桶中元素小于等于 6 时,树结构还原成链表形式。
原因:
- 红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要。
- 链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。
- 中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
IO 流
IO 流的种类
- 按照流的流向,可以分为输入流和输出流;
- 按照操作单元,可以分为字节流和字符流;
- 按照流的角色,可以分为节点流和处理流。
常见的 IO 流
- 输入流
- 字节流
- FileInputStream
- PipedInputStream
- ByteArrayInputStream
- BUfferedInputStream
- DataInputStream
- ObjectInputStream
- SequenceInputStream
- 字符流
- FileReader
- PipedReader
- CharArrayReader
- BufferedReader
- InputStreamReader
- 字节流
- 输出流
- 字节流
- FileOutputStream
- PipedOutputStream
- ByteArrayOutputStream
- BufferedOutputStream
- DataOutputStream
- ObjectOutputStream
- PrintOutputStream
- 字符流(Writer)
- FileWriter
- PipedWriter
- CharArrayWriter
- BufferedWriter
- OutputStreamWriter
- PrintWriter
- 字节流
BIO、NIO、AIO
BIO 是指同步阻塞 IO(Blocking I/O)。一次数据的读取或写入会阻塞当前线程,直到本次数据传输结束。操作简单,适合活动连接数较小的情况。
NIO 是在 Java 1.4 中引入的新的 I/O 模型,因为被称为 New IO。但随着技术的快速发展,NIO 也不再“新”了,因此,我们现在更习惯以它的特性来称其为:同步非阻塞 IO(Non-Blocking I/O)。NIO 提供了 Channel、Selector、Buffer 等抽象,实现了多路复用。此外,NIO 还提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,分别对应 BIO 中的 Socket 和 ServerSocket。
NIO 并非只是非阻塞的,NIO 同时支持阻塞、非阻塞两种模式,只是因为 NIO 主要就是为了提高 IO 性能而诞生的,所以强调了其核心特性:非阻塞。在日常使用中,我们也更为倾向于 NIO 的非阻塞模式,以获得更高的吞吐量和并发量。
AIO 是在 Java 7 中引入的异步非阻塞 IO(Asynchronous I/O)。AIO 是基于事件和回调机制实现的,当操作发生后,会直接得到返回,释放 IO 资源,实际操作的执行则交给其他线程来处理,处理完成后通知相应的线程进行后续的操作。
NIO 的组成
缓冲区(Buffer):用来存储待传输的数据,通过 Channel 进行数据传输。
直接缓冲区(DirectByteBuffer):使用堆外内存创建的缓冲区,可以减少一次堆内内存到堆外内存的数据拷贝。
使用堆外内存创建和销毁缓冲区的成本更高且不可控,通常会使用内存池来提高性能。
通道(Channel):用来建立数据传输需要的连接,并传输 Buffer 中的数据。
数据虽然需要通过 Channel 进行传输,但 Channel 是不直接操作数据的,Channel 只负责建立连接并确认传输内容,实际数据的传输是通过
选择器(Selector):用来管理 Channel 和分配
零拷贝
在 Java 程序中,使用 read() 或 write() 方法拷贝,需要在堆内开辟内存空间存储文件流,再从堆内拷贝到堆外,最后从堆外拷贝到操作系统内核,由 DMA 读写到磁盘。期间需要经过两次复制,且用户态和内核态的交互,因此传输效率较慢。
而在操作系统中提供了 mmap() 方法,我们可以在程序中调用该方法,系统会直接在内核开辟内存空间,直接将文件流传输到内核开辟出的内存空间,由 DMA 读写到磁盘。该方法通过减少文件流的拷贝过程和用户态、内核态的交互,从而提高了文件传输的效率。我们把这种方法,称为“零拷贝”。
当然,零拷贝虽然可以提高文件传输效率,但也并非没有缺点的。由于程序直接传入内核内存空间,在发生 IO 异常、宕机等异常情况下,使用零拷贝有可能会导致数据流的丢失。
反射
反射的实现原理
在 Java 中是通过 Class.forName(classname) 来获取类的信息,实现反射机制的。
注解的实现原理
注解是基于 Java 反射来实现的。
反射是否可以调用私有方法、获取参数名、获取父类私有方法?
可以。我们可以通过反射拿到对应的 class 对象,然后通过 class.getDeclaredConstructors() 拿到全部构造器,获取构造器的名称、参数、修饰符等信息;可以通过 class.getDeclaredMethods() 拿到全部方法,获取方法的名称、参数、修饰符等信息;可以通过 class.getSuperclass().getDeclaredMethod() 获取父类全部方法。
泛型
泛型的类型安全
JDK 1.5 以后引入了泛型的概念,通过泛型能够帮助我们在程序处理中将处理逻辑抽象出来,提高代码复用性。泛型的使用一般遵循类型约束,以此保证泛型类型的安全性。举个例子:
class Person<T> {
// ...
private T atrribute;
public void setAtrribute(T atrribute) {
this.atrribute = atrribute;
}
public T getAtrribute() {
retrun atrribute;
}
}
class PersonAtrribute {
private height;
private vision;
// ...
}
class DuckAtrribute {
private tail;
private wing;
// ...
}
class DemoService() {
Person person = new Person();
PersonAtrribute personAtrribute = new PersonAtrribute();
DuckAtrribute duckAtrribute = new DuckAtrribute();
person.setAtrribute(personAtrribute);
person.setAtrribute(duckAtrribute);
}
上述例子中,DemoService 中错误地将 DuckAtrribute 放入了 Person 的扩展属性中,这显然是不合理的,这也就是我们所关注的泛型安全性问题。
如果没有类型约束,泛型中就可以存放任何类型的东西,那么当你创建这样的一个泛型时,你就无法预知泛型的使用者会拿它做什么。也许有一天,你会发现自己设计的泛型已经在系统里使用地十分混乱,这显然不是我们设计时想要看到的。因此,我们需要对泛型进行约束。
泛型约束包括两种:extends 和 super。extends 决定了泛型的上限,super 决定了泛型的下限。
泛型上限
// 泛型可以接受E类型或者E的子类类型。
? extends E
extends 规定了泛型的上限。当泛型使用 extends 时,使用泛型的类所实现的类型都受 extends 继承类的约束,如果继承了 Person,泛型传进来 Duck 就是不被允许的。
泛型下限
// 可以接受E类型,或者E的父类型。
? super E
super 规定了泛型的下限,当泛型使用 super 时,使用泛型的类所实现的类型都受 super 父类的约束,如果 super 的是一个属性类,属性类里包括 age 和 sex,那么实现泛型的时候就必须要具备这两种属性。
其他
将 ASCII 码转成字符
(char) 98
获取 Class 对象的方法
Class<?> class1= ReflectDemo.class.getClassLoader().loadClass("com.gx.reflectdemo.Person");
Class<?> class2=Class.forName("com.gx.reflectdemo.Person");
Class<?> class3=Person.class;
Class<?> class4=new Person("",1).getClass();
什么是字节码?字节码有哪些使用场景?
字节码是一种介于应用程序和操作系统之间的中间码,需要直译器转义后才能成为操作系统或硬件系统可识别的编码。JVM 的实现就使用了字节码技术,通过读取约定通用的字节码文件(.class 文件),根据不同操作系统来解析成不同的操作系统指令,以此达到跨语言、跨平台的特性。
Tips:字节码文件并非只能是二进制文件!有些文章会认为字节码文件一定是二进制文件,这种认识是错误的!字节码文件是具有固定格式的一类编码文件,只要有能够翻译其格式的直译器,就可以被使用。Java 代码编译后的 .class 文件就是十六进制的编码文件,而 JVM 就是翻译该文件的直译器。
什么是字节码增强技术?字节码增强有哪些使用场景?
字节码增强技术实际上就是在编译期或运行期对字节码进行插桩,以便在运行期采集或修改程序的执行行为。其中动态代理、AOP、Agent 都是与字节码增强技术息息相关的。
字节码增强技术在开发方面可以减少冗余代码,降低代码耦合度、提高开发效率,在部署运维方面可以进行线上性能监测、服务链路追踪等。该技术在各大框架、中间件中都有所使用,如:Spring 框架、Skywalking 等。
——————————————————————————————————————————————
原创:西狩
编写日期 / 修订日期:2021-02-25 / 2021-02-25
版权声明:本文为博主原创文章,遵循 CC BY-NC-SA-4.0 版权协议,转载请附上原文出处链接和本声明。
必知必会之 Java的更多相关文章
- 第5节:Java基础 - 必知必会(下)
第5节:Java基础 - 必知必会(下) 本小节是Java基础篇章的第三小节,主要讲述Java中的Exception与Error,JIT编译器以及值传递与引用传递的知识点. 一.Java中的Excep ...
- 第4节:Java基础 - 必知必会(中)
第4节:Java基础 - 必知必会(中) 本小节是Java基础篇章的第二小节,主要讲述抽象类与接口的区别,注解以及反射等知识点. 一.抽象类和接口有什么区别 抽象类和接口的主要区别可以总结如下: 抽象 ...
- 第3节:Java基础 - 必知必会(上)
第3节:Java基础 - 必知必会(上) 本篇是基础篇的第一小节,我们从最基础的java知识点开始学习.本节涉及的知识点包括面向对象的三大特征:封装,继承和多态,并且对常见且容易混淆的重要概念覆盖和重 ...
- Java并发必知必会第三弹:用积木讲解ABA原理
Java并发必知必会第三弹:用积木讲解ABA原理 可落地的 Spring Cloud项目:PassJava 本篇主要内容如下 一.背景 上一节我们讲了程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单? ...
- 必知必会之Java注解
必知必会之Java注解 目录 不定期更新中-- 元注解 @Documented @Indexed @Retention @Target 常用注解 @Deprecated @FunctionalInte ...
- Java面试必知必会(扩展)——Java基础
float f=3.4;是否正确? 不正确 3.4是双精度,将双精度赋值给浮点型属于向下转型,会造成精度损失: 因此需要强制类型转换: 方式一:float f=(float)3.4 方式二:float ...
- Java面试必知必会:基础
面试考察的知识点多而杂,要完全掌握需要花费大量的时间和精力.但是面试中经常被问到的知识点却没有多少,你完全可以用 20% 的时间去掌握 80% 常问的知识点. 一.基础 包括: 杂七杂八 面向对象 数 ...
- mysql必知必会系列(一)
mysql必知必会系列是本人在读<mysql必知必会>中的笔记,方便自己以后查看. MySQL. Oracle以及Microsoft SQL Server等数据库是基于客户机-服务器的数据 ...
- 《MySQL必知必会》整理
目录 第1章 了解数据库 1.1 数据库基础 1.1.1 什么是数据库 1.1.2 表 1.1.3 列和数据类型 1.1.4 行 1.1.5 主键 1.2 什么是SQL 第2章 MySQL简介 2.1 ...
随机推荐
- Hiho1422 Harmonic Matrix Counter (高斯消元)
16年北京站A题 真的难啊.. 题意: 定义和谐矩阵 就是每个元素和上下左右的xor值=0 输出一个超大数 然后最多800个询问 求字典序第k小的和谐矩阵 x y位置上的数 题解: 首先这个超大数的范 ...
- AtCoder Beginner Contest 178
比赛链接:https://atcoder.jp/contests/abc178/tasks A - not 题意 给出一个整数 $0 \le x \le 1$,如果 $x$ 是 $0$ 就输出 $1$ ...
- Codeforces Round #633 (Div. 2)
Codeforces Round #633(Div.2) \(A.Filling\ Diamonds\) 答案就是构成的六边形数量+1 //#pragma GCC optimize("O3& ...
- P3399 丝绸之路(DP)
题目背景 张骞于公元前138年曾历尽艰险出使过西域.加强了汉朝与西域各国的友好往来.从那以后,一队队骆驼商队在这漫长的商贸大道上行进,他们越过崇山峻岭,将中国的先进技术带向中亚.西亚和欧洲,将那里的香 ...
- 用servlet在网页中打印字符串(初接触)、servlet调用过程
一.servlet是什么: 二.在官方文档中点servlet 这就是servlet的方法,这里说一下什么叫生命周期的方法(life-cycle methods):就是这个对象一旦创生之后一定会执行的方 ...
- c++虚函数、子类中调用父类方法
全部 代码: 1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include< ...
- 部署 WordPress 和 Wecenter
目录 基本环境部署(LNMP) 安装 Nginx 安装 PHP7.1 安装 Mariadb 安装 NFS 部署 NFS 服务端 部署 NFS 客户端 部署 WordPress 首台服务器 环境部署 数 ...
- 使用nodejs爬取图片
在运行代码前,请确保本机是否有nodejs环境 1 D:\ > node -v 2 v12.1.0 //版本号 需要用到的包 axios //请求页面 cheerio // 把get请求的页面 ...
- js arrow function return object
js arrow function return object bug filterData: { type: Object, default: () => {}, required: true ...
- Principle for iOS App Animation Design
Principle for iOS App Animation Design Animate Your Ideas, Design Better Apps https://principleforma ...