必知必会之 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 的组成)
  • 零拷贝

反射

其他

基础知识

数据计量单位

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

什么是深拷贝?

深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。

深拷贝的方法包括:

  1. 重写 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;
    }
    }
  2. 使用序列化实现

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

重写与重载的区别

  1. 重写要求方法名、入参、返回值相同,重载只同名方法的入参不同(类型、个数、顺序至少有一个不同)。
  2. 重写要求子类不能缩小父类方法的访问权限,重载与访问权限无关。
  3. 重写要求子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常),重载与异常范围无关。
  4. 重写是子类对父类方法的覆盖行为,重载是一个类的多态性。
  5. 重写方法不能被定义为 final,重载方法可以被定义为 final。

类加载机制

什么是双亲委派?

在类加载过程中,子类会先去父类查找,如果找到,则从父类缓存加载。如果没找到,再由父类指派子类进行加载。

双亲委派机制主要出于安全来考虑。比如自定义 java.lang.String,如果不先去父类查找,相当于 Bootstrap 加载器的 java.lang.String 被篡改了。

如何打破双亲委派?

  1. 重写 loadClass() 方法

    JDK 1.2 之前,自定义 ClassLoader 都必须重写 loadClass()

  2. ThreadContextClassLoader 可以实现基础类调用实现类代码,通过 thread.setContextClassLoader 指定

  3. 热启动,热部署

    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的更多相关文章

  1. 第5节:Java基础 - 必知必会(下)

    第5节:Java基础 - 必知必会(下) 本小节是Java基础篇章的第三小节,主要讲述Java中的Exception与Error,JIT编译器以及值传递与引用传递的知识点. 一.Java中的Excep ...

  2. 第4节:Java基础 - 必知必会(中)

    第4节:Java基础 - 必知必会(中) 本小节是Java基础篇章的第二小节,主要讲述抽象类与接口的区别,注解以及反射等知识点. 一.抽象类和接口有什么区别 抽象类和接口的主要区别可以总结如下: 抽象 ...

  3. 第3节:Java基础 - 必知必会(上)

    第3节:Java基础 - 必知必会(上) 本篇是基础篇的第一小节,我们从最基础的java知识点开始学习.本节涉及的知识点包括面向对象的三大特征:封装,继承和多态,并且对常见且容易混淆的重要概念覆盖和重 ...

  4. Java并发必知必会第三弹:用积木讲解ABA原理

    Java并发必知必会第三弹:用积木讲解ABA原理 可落地的 Spring Cloud项目:PassJava 本篇主要内容如下 一.背景 上一节我们讲了程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单? ...

  5. 必知必会之Java注解

    必知必会之Java注解 目录 不定期更新中-- 元注解 @Documented @Indexed @Retention @Target 常用注解 @Deprecated @FunctionalInte ...

  6. Java面试必知必会(扩展)——Java基础

    float f=3.4;是否正确? 不正确 3.4是双精度,将双精度赋值给浮点型属于向下转型,会造成精度损失: 因此需要强制类型转换: 方式一:float f=(float)3.4 方式二:float ...

  7. Java面试必知必会:基础

    面试考察的知识点多而杂,要完全掌握需要花费大量的时间和精力.但是面试中经常被问到的知识点却没有多少,你完全可以用 20% 的时间去掌握 80% 常问的知识点. 一.基础 包括: 杂七杂八 面向对象 数 ...

  8. mysql必知必会系列(一)

    mysql必知必会系列是本人在读<mysql必知必会>中的笔记,方便自己以后查看. MySQL. Oracle以及Microsoft SQL Server等数据库是基于客户机-服务器的数据 ...

  9. 《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 ...

随机推荐

  1. Hiho1422 Harmonic Matrix Counter (高斯消元)

    16年北京站A题 真的难啊.. 题意: 定义和谐矩阵 就是每个元素和上下左右的xor值=0 输出一个超大数 然后最多800个询问 求字典序第k小的和谐矩阵 x y位置上的数 题解: 首先这个超大数的范 ...

  2. AtCoder Beginner Contest 178

    比赛链接:https://atcoder.jp/contests/abc178/tasks A - not 题意 给出一个整数 $0 \le x \le 1$,如果 $x$ 是 $0$ 就输出 $1$ ...

  3. Codeforces Round #633 (Div. 2)

    Codeforces Round #633(Div.2) \(A.Filling\ Diamonds\) 答案就是构成的六边形数量+1 //#pragma GCC optimize("O3& ...

  4. P3399 丝绸之路(DP)

    题目背景 张骞于公元前138年曾历尽艰险出使过西域.加强了汉朝与西域各国的友好往来.从那以后,一队队骆驼商队在这漫长的商贸大道上行进,他们越过崇山峻岭,将中国的先进技术带向中亚.西亚和欧洲,将那里的香 ...

  5. 用servlet在网页中打印字符串(初接触)、servlet调用过程

    一.servlet是什么: 二.在官方文档中点servlet 这就是servlet的方法,这里说一下什么叫生命周期的方法(life-cycle methods):就是这个对象一旦创生之后一定会执行的方 ...

  6. c++虚函数、子类中调用父类方法

    全部 代码: 1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include< ...

  7. 部署 WordPress 和 Wecenter

    目录 基本环境部署(LNMP) 安装 Nginx 安装 PHP7.1 安装 Mariadb 安装 NFS 部署 NFS 服务端 部署 NFS 客户端 部署 WordPress 首台服务器 环境部署 数 ...

  8. 使用nodejs爬取图片

    在运行代码前,请确保本机是否有nodejs环境 1 D:\ > node -v 2 v12.1.0 //版本号 需要用到的包 axios //请求页面 cheerio // 把get请求的页面 ...

  9. js arrow function return object

    js arrow function return object bug filterData: { type: Object, default: () => {}, required: true ...

  10. Principle for iOS App Animation Design

    Principle for iOS App Animation Design Animate Your Ideas, Design Better Apps https://principleforma ...