Java高级学习笔记
Java高级学习笔记
学习来源:尚硅谷
学习时间:2022年3月23日
1 多线程
略,详见实用技术-Java并发编程学习笔记
2 常用类
2.1 字符串相关的类
2.1.1 概述
String类:代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
- String是一个final类,代表不可变的字符序列。
- 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
- String实现了
Serializable
接口,表示字符串是支持序列化的;实现了Comparable
接口,表示字符串可以比较大小 - String对象的字符内容是存储在一个字符数组
value[]
中的。 String类的部分源码:
1
2
3
4
5
6
7
8
9public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char[] value; // final修饰,代表不可变
private int hash;
// ...
}
2.1.2 String的不可变性
通过字面量的方式(区别于new
)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
字符串常量池中不会存储相同内容的字符串。
- 不可变性
- 当对字符串重新赋值时,需要新开辟内存区域进行赋值,不能改变原有的内存区域存储的值
- 当对现有的字符串进行连接操作时,也需要新开辟内存区域进行赋值
- 当调用String的方法修改指定的字符或字符串时,也需要新开辟内存区域进行赋值
代码示例和图演示
1 | String s1 = "abc"; // 字面量定义字符串 |
1 | String s1 = "abc"; |
1 | String s1 = "abc"; |
1 | String s1 = "abc"; |
2.1.3 String对象的创建
① 概述和使用
可以通过字面量或者new + 构造器
的方式来创建String对象:
1 | String str = "hello"; |
字面量和new的区别
字符串常量存储在字符串常量池,目的是共享
字符串非常量对象存储在堆中。
代码示例1
1 | static void test2() { |
代码示例2
1 | Person p1 = new Person("Tom", 12); |
1 | Person p1 = new Person("Tom", 12); |
问答题
1 | String s = new String("abc"); |
问:在内存中创建了几个对象?
答:两个,一个是堆空间中的String对象,一个是String对象中char[]属性对应的常量池中的数据。
② 不同拼接操作的对比
- 常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量
- 只要拼接内容中有一个是变量,结果就在堆中,相当于new了一个字符串
- 如果拼接的结果调用
intern()
方法,返回值就在常量池中
代码示例
1 |
|
图示:
1 |
|
注意:由final
修饰的是常量,即s2是常量,不是变量。因此第五句代码本质上是常量与常量的拼接。
面试题
1 | public class StringTest { |
说明:关于值传递:
- 基本数据类型传递的是数据本身
- 引用数据类型传递的是地址值
2.1.4 常用方法
int length()
:返回字符串的长度char charAt(int index)
:返回某索引处的字符isEmpty()
:判断是否是空字符串String toLowerCase()
:使用默认语言环境,将 String 中的所有字符转换为小写String toUpperCase()
:使用默认语言环境,将 String 中的所有字符转换为大写String trim()
:返回字符串的副本,忽略前导空白和尾部空白boolean equals(Object obj)
:比较字符串的内容是否相同boolean equalsIgnoreCase(String anotherString)
:与equals方法类似,忽略大小写String concat(String str)
:将指定字符串连接到此字符串的结尾,等价于用“+”int compareTo(String anotherString)
:比较两个字符串的大小,返回差值String substring(int beginIndex)
:返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串(左闭右开)。boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的子字符串是否以指定前缀开始boolean contains(CharSequence s)
:当且仅当此字符串包含指定的 char 值序列时,返回 trueint indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引,找不到返回-1int indexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始int lastIndexOf(String str)
:返回指定子字符串在此字符串中最右边出现处的索引int lastIndexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索String replace(char oldChar, char newChar)
:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的String replace(CharSequence target, CharSequence replacement)
:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串String replaceAll(String regex, String replacement)
: 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串String replaceFirst(String regex, String replacement)
: 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式String[] split(String regex)
:根据给定正则表达式的匹配拆分此字符串String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
代码示例1
1 |
|
代码示例2
1 |
|
代码示例3
1 |
|
代码示例4
1 |
|
代码示例5
1 |
|
2.1.5 数据转换
① 基本数据类型
- 字符串 —> 基本数据类型、包装类
- Integer包装类的
public static int parseInt(String s)
:可以将由“数字”字符组成的字符串转换为整型。 - 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
- Integer包装类的
1 |
|
- 基本数据类型、包装类 —> 字符串
- 调用String类的
public String valueOf(int n)
可将int型转换为字符串 - 相应的重载方法:valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类型到字符串的转换
- 调用String类的
1 |
|
② char数组
- 字符数组 —> 字符串
- String 类的构造器:
String(char[]) 和 String(char[], int offset, int length)
分别用字符数组中的全部字符和部分字符创建字符串对象。
- String 类的构造器:
1 |
|
- 字符串 —>字符数组
public char[] toCharArray()
:将字符串中的全部字符存放在一个字符数组中的方法。public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
:提供了将指定索引范围内的字符串存放到数组中的方法。
1 |
|
③ byte数组
- 字节数组 —> 字符串
String(byte[])
:通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。String(byte[], int offset, int length)
:用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。
1 |
|
- 字符串 —> 字节数组
public byte[] getBytes()
:使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。public byte[] getBytes(String charsetName)
:使用指定的字符集将 此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
1 |
|
2.1.6 StringBuffer和StringBuilder类
① 介绍
java.lang.StringBuffer
代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。很多方法与String相同。作为参数传递时,方法内部可以改变值。
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样
String、StringBuffer、StringBuilder三者异同
String
(JDK1.0):不可变字符序列,底层使用char[]
存储字符StringBuffer
(JDK1.5):可变字符序列、效率低、线程安全,底层使用char[]
存储字符1
2
3
4
5
6
7
public void test0() {
StringBuffer sb1 = new StringBuffer("abc");
// 该方法没有返回值,修改的就是sb1本身的值
sb1.setCharAt(0, 'm');
System.out.println(sb1); // mbc
}StringBuilder
(JDK5.0):可变字符序列、效率高、线程不安全,底层使用char[]
存储字符- 注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。
② StringBuffer类
StringBuffer源码
源码分析
- 对于String
1 | String s1 = new String(); // 实质: char[] value = new char[0]; |
对于StringBuffer
构造器和有效长度:StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:
StringBuffer()
:初始容量为16的字符串缓冲区StringBuffer(int size)
:构造指定容量的字符串缓冲区StringBuffer(String str)
:将内容初始化为指定字符串内容
1
2
3
4
5
6
7StringBuffer sb1 = new StringBuffer(); // char[] value = new char[16];底层创建了一个长度是16的char数组value
sb1.append('a'); // value[0] = 'a';
sb1.append('b'); // value[1] = 'b';
System.out.println(sb1.length()); // 返回count(有效字符的个数)为2
StringBuffer sb2 = new StringBuffer("abc"); // char[] value = new char["abc".length() + 16];创建后额外加16个空间
System.out.println(sb2.length()); // 返回count(有效字符的个数)为3扩容:如果要添加的数据对底层数组盛不下了,那就需要扩容
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 AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
int newCapacity = (oldCapacity << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;
return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}总结:默认情况下,扩容为原来容量的
2倍+2
,同时将原有数组的元素复制到这个扩容后的数组当中。指导意义:建议使用带参数的构造器
StringBuffer(int capacity)
和StringBuilder(int capacity)
,然后再考虑线程安全的问题
常用方法
StringBuffer append(xxx)
:提供了很多重载的append()方法,用于进行字符串拼接StringBuffer delete(int start,int end)
:删除指定位置的内容,左闭右开StringBuffer replace(int start, int end, String str)
:把[start,end)位置替换为strStringBuffer insert(int offset, xxx)
:在指定位置插入xxxStringBuffer reverse()
:把当前字符序列逆转public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
代码示例
1 |
|
以上方法支持方法链
1 | StringBuffer s1 = new StringBuffer("abc"); |
③ StringBuilder类
StringBuilder和StringBuffer底层实现基本一致,方法与StringBuffer也一致,故略。
三者效率对比
1 |
|
1 | StringBuffer的执行时间:4 |
例题
1 |
|
解析:
append()
方法在添加null
时,调用appendNull()
:该方法会把null看作是字符串
1 | private AbstractStringBuilder appendNull() { |
- 调用构造器,将null传入进去:
1 | public StringBuffer(String str) { |
2.2 日期时间相关的类
2.2.1 JDK8之前的API
① System类
System类提供的public static long currentTimeMillis()
用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。此方法适于计算时间差。
计算世界时间的主要标准有:
UTC(Coordinated Universal Time)
GMT(Greenwich Mean Time)
CST(Central Standard Time)
代码演示
1 |
|
② Date类
表示特定的瞬间,精确到毫秒。
构造器
Date()
:使用无参构造器创建的对象可以获取本地当前时间。Date(long date)
:其他参数的构造器已被弃用
常用方法
getTime()
:返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。toString()
:把此 Date 对象转换为以下形式的 String:dow mon dd hh:mm:ss zzz yyyy
其中:dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。- 其它很多方法都过时了。
代码演示
1 |
|
注意
java.sql.Date
继承了java.util.Date
,对应着数据库中的日期类型的变量。
1 |
|
java.util.Date
—>java.util.Date
1 |
|
③ SimpleDateFormat类
Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat
类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
它允许进行格式化:日期 —> 文本、解析:文本 —> 日期
格式化
SimpleDateFormat()
:默认的模式和语言环境创建对象public SimpleDateFormat(String pattern)
:该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用。其中pattern格式:
public String format(Date date)
:方法格式化时间对象date
解析
public Date parse(String source)
:从给定字符串的开始解析文本,以生成一个日期。
代码示例
1 |
|
④ Calendar类
略
2.2.2 JDK8中新的日期时间API
① 新API的引入
如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
- 格式化:格式化只对Date有用,Calendar则不行。
- 此外,它们也不是线程安全的;不能处理闰秒等。
第三次引入的API是成功的,并且Java 8中引入的java.time
API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。
新日期时间API
java.time
– 包含值对象的基础包java.time.chrono
– 提供对不同的日历系统的访问java.time.format
– 格式化和解析时间和日期java.time.temporal
– 包括底层框架和扩展特性java.time.zone
– 包含时区支持的类
说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
② LocalDate类
LocalDate
、LocalTime
、LocalDateTime
类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
LocalDate
代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。LocalTime
表示一个时间,而不是日期。LocalDateTime
是用来表示日期和时间的,这是一个最常用的类之一。
常用方法
代码示例
1 |
|
③ Instant类
Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。
在处理时间和日期的时候,我们通常会想到年,月,日,时,分,秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。
java.time
包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。
常用方法
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。
代码示例
1 |
|
④ DateTimeFormatter类
java.time.format.DateTimeFormatter
类:该类提供了三种格式化方法:
- 预定义的标准格式。如:
ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
- 本地化相关的格式。如:
ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如:
ofPattern("yyyy-MM-dd hh:mm:ss")
常用方法
ofPattern(String pattern)
静态方法 , 返 回 一 个 指 定 字 符 串 格 式DateTimeFormatter
format(TemporalAccessor t)
格式化一个日期、时间,返回字符串parse(CharSequence text)
将指定格式的字符序列解析为一个日期、时间
代码示例
1 |
|
⑤ 其他API
略
2.3 比较器
在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
Java实现对象排序的方式有两种:
- 自然排序:
java.lang.Comparable
- 定制排序:
java.util.Comparator
2.3.1 自然排序
① 概述
Comparable
接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
实现 Comparable 的类必须实现 compareTo(Object obj)
方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。
实现Comparable接口的对象列表(和数组)可以通过 Collections.sort
或Arrays.sort
进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparable 的典型实现:(默认都是从小到大排列的)
- String:按照字符串中字符的Unicode值进行比较
- Character:按照字符的Unicode值来进行比较
- 数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
- Boolean:true 对应的包装类实例大于 false 对应的包装类实例
- Date、Time等:后面的日期时间比前面的日期时间大
② 实例及使用
- 像String,包装类等实现了
Comparable
接口,重写了compareTo()
方法,给出了比较两个对象大小的方式。 - 重写
compareTo()
的规则:- 如果当前对象this大于形参对象obj,则返回正整数
- 如果当前对象this小于形参对象obj,则返回负整数
- 如果当前对象this等于形参对象obj,则返回零。
- 对于自定义类来说,如果需要排序,可以让自定义类重写Comparable接口,重写compareTo方法来指明如何排序。
String重写的
compareTo()
1 | public int compareTo(String anotherString) { |
代码示例——String实现自然排序
1 |
|
代码示例——自定义类实现自然排序
- 要排序的商品类
1 |
|
- 测试方法
1 |
|
2.3.2 定制排序
① 概述
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable
接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator
的对象来排序,强行对多个对象进行整体排序的比较。
- 重写
compare(Object o1,Object o2)
方法:- 比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2
- 如果返回0,表示相等
- 返回负整数,表示o1小于o2。
可以将 Comparator 传递给 sort 方法(如 Collections.sort
或 Arrays.sort
),从而允许在排序顺序上实现精确控制。
还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
② 实例及使用
代码示例——String类实现Comparator接口
1 |
|
代码示例——自定义类
1 |
|
③ 二者对比
Comparable
一旦指定,能够保证实现类的对象能在任何位置都可以比较大小Comparator
属于临时性的比较
2.4 System类
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
成员变量
System类内部包含in
、out
和err
三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
成员方法
native long currentTimeMillis()
: 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。void exit(int status)
: 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。void gc()
: 该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。String getProperty(String key)
: 该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
代码示例
1 |
|
3 枚举类和注解
3.1 枚举类的使用
3.1.1 概述
类的对象只有有限个,确定的。举例如下:
- 星期:Monday(星期一)、……、Sunday(星期天)
- 性别:Man(男)、Woman(女)
- 季节:Spring(春节)……Winter(冬天)
- 支付方式:Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银
行卡)、CreditCard(信用卡) - 就职状态:Busy、Free、Vocation、Dimission
- 订单状态:Nonpayment(未付款)、Paid(已付款)、Delivered(已发货)、Return(退货)、Checked(已确认)Fulfilled(已配货)
- 线程状态:创建、就绪、运行、阻塞、死亡
当需要定义一组常量时,强烈建议使用枚举类。
枚举类的实现
JDK1.5之前需要自定义枚举类
JDK1.5新增的
enum
关键字用于定义枚举类
若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
枚举类的属性
枚举类对象的属性不应允许被改动,所以应该使用
private final
修饰枚举类的使用
private final
修饰的属性应该在构造器中为其赋值若枚举类显式的定义了带参数的构造器,则在列出枚举值时也必须对应的传入参数
3.1.2 自定义枚举类
JDK1.5之前需要自定义枚举类
- 私有化类的构造器,保证不能在类的外部创建其对象
- 在类的内部创建枚举类的实例,声明为:
public static final
- 对象如果有实例变量,应该声明为
private final
,并在构造器中初始化
1 | public class EnumTest { |
3.1.3 使用Enum定义枚举类
① 使用说明
- 使用
enum
定义的枚举类默认继承了java.lang.Enum
类,因此不能再继承其他类 - 枚举类的构造器只能使用
private
权限修饰符 - 枚举类的所有实例必须在枚举类中显式列出(
,
分隔;
结尾)。列出的实例系统会自动添加public static final
修饰 - 必须在枚举类的第一行声明枚举类对象
JDK 1.5 中可以在 switch 表达式中使用Enum定义的枚举类的对象作为表达式,case 子句可以直接使用枚举值的名字,无需添加枚举类作为限定。
代码示例
1 | public class EnumTest { |
② Enum类的主要方法
values()
:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。valueOf(String str)
:可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException
toString()
:返回当前枚举类对象常量的名称
代码示例
1 |
|
③ 实现接口的枚举类
- 和普通 Java 类一样,枚举类可以实现一个或多个接口
- 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
- 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
代码示例
- 情况一:若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可
1 | public class EnumTest { |
- 情况二:若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
1 | public class EnumTest { |
3.2 注解的使用
3.2.1 概述
从 JDK 5.0
开始,Java 增加了对元数据(MetaData) 的支持,也就是Annotation(注解) 。
Annotation 其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用 Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
Annotation 可以像修饰符一样被使用,可用于修饰包、类、构造器、方法,、成员变量、参数、局部变量的声明,这些信息被保存在 Annotation 的 name=value
对中。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:==框架 = 注解 + 反射 + 设计模式==。
3.2.2 常见的Annotation示例
使用 Annotation 时要在其前面增加 @
符号,,并把该 Annotation 当成一个修饰符使用,用于修饰它支持的程序元素。
示例1——生成文档相关的注解
@author
标明开发该类模块的作者,多个作者之间使用,分割@version
标明该类模块的版本@see
参考转向,也就是相关主题@since
从哪个版本开始增加的@param
对方法中某参数的说明,如果没有参数就不能写@return
对方法返回值的说明,如果方法的返回值类型是void就不能写@exception
对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写
示例2——在编译时进行格式检查(JDK内置的三个基本注解)
@Override
: 限定重写父类方法, 该注解只能用于方法@Deprecated
: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择。@SuppressWarnings
: 抑制编译器警告
1 | public class AnnotationTest { |
示例3——跟踪代码依赖性,实现替代配置文件功能
- Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。
- spring框架中关于“事务”的管理
3.2.3 自定义注解
① 基本使用
- 定义新的 Annotation 类型使用
@interface
关键字 - 自定义注解自动继承了java.lang.annotation.Annotation接口
- Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
- 可以在定义 Annotation 的成员变量时为其指定初始值,指定成员变量的初始值可使用
default
关键字 - 如果只有一个参数成员,建议使用参数名为value
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
- 没有成员定义的 Annotation 称为标记(例如
@Override
),包含成员变量的 Annotation 称为元数据Annotation - 注意:自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
代码示例
- 自定义注解类
1 | public MyAnnotation { |
- 使用
1 |
|
② 基本元注解
JDK 的元 Annotation 用于修饰其他 Annotation 定义,对现有的注解进行说明的注解。
JDK5.0提供了4个标准的meta-annotation类型,分别是:
Retention
Target
Documented
Inherited
@Retention
- 只能用于修饰一个 Annotation 定义,用于指定该 Annotation 的生命周期,@Rentention 包含一个
RetentionPolicy
类型的成员变量,使用@Rentention 时必须为该 value 成员变量指定值RetentionPolicy.SOURCE
:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释。RetentionPolicy.CLASS
:在class文件中有效(即class保留),当运行 Java 程序时,JVM 不会保留注解。 这是默认值。RetentionPolicy.RUNTIME
:在运行时有效(即运行时保留),当运行 Java 程序时,JVM 会保留注释。程序可以通过反射获取该注释。
1 |
|
@Target
用于修饰 Annotation 定义,用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。
取值(ElementType) | 说明 |
---|---|
CONSTRUCTOR |
构造器 |
FIELD |
域 |
LOCAL_VIRIABLE |
局部变量 |
METHOD |
方法 |
PACKAGE |
包 |
PARAMETER |
参数 |
TYPE |
类、接口、enum |
@Documented
用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
定义为Documented的注解必须设置Retention值为RUNTIME
@Inherited
被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的 Annotation,则其子类将自动具有该注解。使用很少。
4 集合
4.1 集合框架概述
集合,数组都是对多个数据进行存储的结构,简称Java容器。说明:此时的存储是内存层面的存储,不涉及持久化的存储。
java集合可分为Collection和Map两种体系。
- Collection接口:单列数据,定义了存取一组对象的方法和集合。
- List:元素有序,可重复的集合
- Set:元素无序,不可重复的集合
- Map接口:双列数据,保存具有映射关系Key-Value对的集合。
4.1.1 数组的特点
数组在存储多个数据方面的特点
一旦初始化以后,其长度就确定了
数组一旦定义好,其元素的类型也就确定了,例如String[] arr;Object[] arr等
数组在存储多个数据方面的缺点
一旦初始化后,其长度就无法修改
数组中提供的方法非常有限,对于添加,删除,插入数据等操作非常不便,同时效率不高。
对于获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序,可重复。对于无序,不可重复的需求,数组不能满足。
4.1.2 集合框架
|——Collection接口:单列集合,用来存储一个一个的对象
|——List接口:存储有序的,可重复的数据。“动态数组”
|——ArrayList,LinkedList,Vector
|——Set接口:存储无序的,不可重复的数据。高中讲的“集合”
|——HashSet,LinkedHashSet,TreeSet
|——Map接口:双列集合,用来存储一对一对的数据。 高中讲的“函数”:y = f(x)
|——HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
4.2 Collection接口
4.2.1 Collection接口中的常用方法
add()
,addAll()
,clear()
,size()
示例代码:
1 |
|
contains()
,containsAll()
示例代码:
1 |
|
Person
类中重写的equals
方法:
1 | // idea自动生成的重写Object类里的equals方法 |
remove()
,removeAll()
1 |
|
retainAll()
1 | coll.retainAll(coll1);// 获取coll和coll1的交集,并返回给coll |
equals()
示例代码:
1 |
|
hashCode()
,toArray()
,Arrays.asList()
示例代码:
1 |
|
4.2.2 集合元素的遍历
集合元素的遍历操作,需要使用Iterator
接口。
- 设计模式给迭代器模式的定义为:提供一种方法访问一个容器对象中的各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
- Collection接口继承了
java.lang.Iterable
接口,该接口有一个iterator()
方法,那么所有实现了Collection接口的集合类都有一个iterator方法,用以返回一个实现了Iterator接口的对象。 - Iterator仅用于遍历集合,其本身不具有提供承载对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
- ==集合对象每次调用iterator方法都得到一个全新的迭代器对象==,默认游标都在集合的第一个元素之前。
遍历代码演示
1 |
|
迭代器原理
用迭代器遍历的错误写法
1 | Iterator iterator = coll.iterator(); |
迭代器的
remove()
可以在遍历的时候删除集合中的元素。此方法不同于集合直接调用remove
方法
代码示例:
1 |
|
注意:如果还未调用next就调用remove,或者调用一次remove后再次调用remove,会报IllegalStateException
异常。
4.2.3 foreach
循环遍历
jdk5.0
新增特性,用于遍历数组和集合。
代码演示
1 |
|
一个练习题
1 |
|
4.3 List接口及其实现类
4.3.1 List概述
- List接口是Collection的子接口,通常使用List来替代数组。
- List集合中的元素有序且可重复,每个元素都有对应的索引顺序,可根据整型序号来对元素进行存取。
- List接口的常用实现类有
ArrayList
,LinkedList
和Vector
面试题:实现类三者的异同?
同:都是List接口的实现类,存储的都是有序可重复的数据。
异:
|——Collection接口:单列集合,用来存储一个一个的对象
|——List接口:存储有序的,可重复的数据。“动态数组”
|——ArrayList:JDK1.2
,作为List接口的主要实现类,线程不安全的,效率高;底层采用Object[] elementData
存储(顺序表
)
|——LinkedList:JDK1.2
,底层采用的双向链表
存储,对于频繁的插入和删除操作的效率比上者高。
|——Vector:JDK1.0
,是List接口的古老实现类,线程安全的,效率低
4.3.2 ArrayList源码分析
JDK7
版本——饿汉式
1 | ArrayList list = new ArrayList();// 底层创建了长度是10的Object[]数组elementData |
结论:实际开发中使用带参的构造器,指定出大小:
1 | ArrayList list = new ArrayList(int capacity); |
JDK8
版本——懒汉式
1 | ArrayList list = new ArrayList();// 底层Object[] elementData初始化为{},并没有创建长度为10的数组 |
小结
jdk7的ArrayList对象的创建类似于单例的饿汉式,jdk8类似于单例的懒汉式,延迟了数组的创建,节省内存。
4.3.3 LinkedList源码分析
JDK8
版本
- 结点结构:
1 | // 作为LinkedList类的内部类 |
- 添加元素(连接在链表尾部)
1 | void linkLast(E e) { |
- 分析
1 | LinkedList list = new LinkedList();// 内部声明了Node类型的first和last属性,默认值为null |
4.3.4 List接口的常用方法
List接口除了有从Collection接口继承的方法外,还添加了一些根据索引来操作集合元素的方法。
代码演示说明
1 |
|
总结常用方法
增:
add(Object obj)
删:
remove(int index)
/remove(Object obj)
改:
set()
查:
get()
插:
add()
长度:
size()
遍历:1.Iterator迭代器 2.增强for循环 3.普通循环
4.3.5 List集合的遍历
代码演示
1 |
|
一道笔试题
1 |
|
注意区分List中的remove
方法
4.4 Set接口及其实现类
4.4.1 Set概述
Set接口中没有额外定义新的方法,使用的都是Collection接口定义的方法
框架
|——Collection接口:单列集合,用来存储一个一个的对象
|——Set接口:存储==无序的,不可重复==的数据。高中讲的“集合”
|——HashSet:作为Set的主要实现类;线程不安全,可以存储null值
|——LinkedHashSet:作为HashSet的子类;遍历其内部数据时可以按照添加的顺序去遍历
|——TreeSet:可以按照添加的对象的指定属性进行排序,底层采用==红黑树==
无序和无可重复的理解
- 无序性:不等于随机性。以HashSet为例,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加。
- 不可重复性:保证添加的元素按照
equal
方法判断时不能返回true
,即相同的元素只能添加一个。
4.4.2 HashSet添加元素的过程
向HashSet中添加元素a,首先,调用a的所在类的hashCode方法,计算出a元素的哈希值,此哈希值接着通过某种算法(例如除留余数法)计算出a在HashSet底层数组中的存放位置,即为索引位置。判断数组此位置上是否已经含有元素:
|—-如果此位置上没有其他元素,则a添加成功;—->情况1
|—-如果此位置上有其他元素b(或以链表形式存在的多个元素),首先比较a和b的哈希值:
|—-如果哈希值不相同,则a添加成功;—->情况2
|—-如果哈希值相同,则需要调用a所在类的equals方法,与链表上的元素逐一相比:
|—-如果一旦返回true,则添加失败;
|—-如果比较到最后返回false,则a添加成功;—->情况3
对于添加成功的情况2和情况3,元素a与已经存在指定索引位置上的数据以链表方式进行存储。在JDK7中,元素a放在数组中,指向原来的元素链;在JDK8中,原来的链尾元素指向新添加进来的元素a。(==7上8下==)
HashSet的底层为:==数组+链表==,实质上还是new了一个HashMap
4.4.3 关于equals()
和hashCode()
方法的重写
idea中自动生成的重写方法
1 |
|
选择31作为乘数的原因:选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)。并且31只占用5bits,相乘造成数据溢出的概率较小。31可以由i*31== (i<<5)-1
来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)。31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
重写原则
向set添加的元素所在的类必须重写hashCode和equals方法。
重写的两个方法要保持一致性:相等的对象必须有相等的哈希值
重写hashCode方法的原则
在程序运行时,同一个对象多次调用
hashCode()
方法应该返回相同的值。当两个对象的
equals()
方法比较返回 true 时,这两个对象的hashCode()
方法的返回值也应相等。对象中用作
equals()
方法比较的属性,都应该用来计算 hashCode 值。
重写equals方法的原则
当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。
因此,违反了“相等的对象必须具有相等的散列码”。
结论:==复写equals方法的时候一般都需要同时复写hashCode方法==。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
4.4.4 LinkedHashSet的使用
LinkedHashSet是作为HashSet的子类,在添加数据的同时,还维护了每个数据的添加的先后顺序。即每个结点有前后两个指针域,指示上一个和下一个元素的位置。对于频繁的遍历操作,效率比HashSet高。
代码演示
1 | // LinkedHashSet的使用 |
原理示意图
4.4.5 TreeSet的使用
- 向TreeSet中添加的数据,要求是==相同类的对象==
1 |
|
- 可以按照排序后输出
1 |
|
- TreeSet的底层是==红黑树==
TreeSet的自然排序(要求比较对象所在类实现Comparable接口)
在TreeSet自然排序中,比较两个对象是否相同的标准为:compareTo()
返回值为0,不再是用equals()
判断。
1 |
|
1 | public class User implements Comparable{ |
TreeSet的定制排序(要求TreeSet的构造器参数为实现Comparator接口的对象)
在TreeSet定制排序中,比较两个对象是否相同的标准为:compare()
返回值为0,不再是用equals()
判断。
1 |
|
4.5 Map接口及其实现类
4.5.1 Map框架概述
|——Map接口:双列集合,用来存储一对一对的数据。 高中讲的“函数”:y = f(x)
|——HashMap:作为Map的主要实现类。线程不安全,效率高。可以存储null
的key和value。
|——LinkedHashMap:保证在遍历Map元素时是按照添加的顺序实现遍历。在原有的HashMap的底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类的执行效率要高于HashMap。
|——TreeMap:保证按照添加的键值对进行排序,实现排序遍历。此时考虑key的自然或定制排序。底层是红黑树
|——Hashtable:注意t小写。Map的古老实现类。线程安全,效率低。不能存储null
的key和value
|——Properties:常用来处理配置文件。key和value都是String类型。
HashMap底层
jdk7
数组+链表jdk8
数组+链表+==红黑树==
典型面试题
HashMap的底层实现原理
HashMap和Hashtable的异同
4.5.2 Map结构的理解:key和value的特点
Map中的key:无序不可重复,使用Set存储所有的key;要求key所在类重写equals和hashCode方法。
Map中的value:无序可重复,使用Collection存储所有的value;要求key所在类重写equals方法。
一个键值对构成了一个Entry对象
- Map中的Entry:无序不可重复,使用Set存储所有的entry
4.5.3 HashMap的底层实现原理
JDK7
版本
1 | HashMap map = new HashMap(); |
在实例化以后,底层创建了长度是==16==的一维数组Entry[] table
1 | // 可能已经执行过多次put操作 |
首先,调用key1所在类的hashCode方法计算key1的哈希值,此哈希值经过某种算法后(例如取余),得到在Entry数组中的存放位置。
|——如果此位置上为空,则entry对象添加成功;情况1
|——如果此位置上不为空,意味着此位置上存在一个或多个数据(以链表形式存在),则比较key1和已经存在的数据的哈希值:
|——如果都不相同,则添加成功;情况2
|——如果与某一个数据的哈希值相同,则调用key1所在类的equals方法进行比较:
|——如果返回false,则添加成功;情况3
|——如果返回true,使用value1==替换==相同key的value值。
- 对于情况2和情况3:同HashSet一样七上八下。
- 扩容:默认的扩容方式为扩容为原来容量的==2倍==(即新开辟一个原来容量2倍的数组空间),并将原有数据复制到该新数组中。
JDK8
版本
1 | HashMap map = new HashMap(); |
- 底层没有创建一个长度为16的数组,并且该数组类型不是Entry了,而是Node;
- 首次调用put方法时,才创建数组。
- 底层结构新增了==红黑树==。当数组的某一个索引位置上的元素以链表形式存在的==个数大于8且当前数组的长度大于64时==,此索引位置上的所有数据改为使用红黑树存储。
4.5.4 HashMap源码分析
JDK7
版本
暂略
4.5.5 LinkedHashMap原理及使用
4.5.6 Hashtable原理及使用
4.5.7 Properties原理及使用
4.6 Collections工具类
4.6.1 介绍
Collections 是一个操作 Set、List 和 Map 等集合的工具类。
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
4.6.2 常用方法
① 排序
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List, Comparator)
:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List, int i, int j)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换
代码示例
1 |
|
② 查找和替换
Object max(Collection)
:根据元素的自然顺序,返回给定集合中的最大元素Object max(Collection, Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素Object min(Collection)
Object min(Collection, Comparator)
int frequency(Collection, Object)
:返回指定集合中指定元素的出现次数void copy(List dest, List src)
:将src中的内容复制到dest中boolean replaceAll(List list, Object oldVal, Object newVal)
:使用新值替换List 对象的所有旧值
代码演示
1 |
|
③ 同步控制
Collections 类中提供了多个 synchronizedXxx()
方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
代码示例
1 |
|
5 泛型
5.1 概念
5.1.1 泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不缺定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>
,List<E>
,ArrayList<E>
这个`
5.1.2 泛型的概念
- 泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
- 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:
List<String>
,这表明该List只能保存字符串类型的对象。 - JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
5.1.3 需要泛型的理由
- 为什么要有泛型呢,直接Object不是也可以存储数据吗?
- 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
- 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException
异常。同时,代码更加简洁、健壮。
代码示例
1 |
|
5.2 集合中使用泛型
以ArrayList和HashMap为例
5.2.1 ArrayList
代码示例
1 |
|
5.2.2 HashMap
代码示例
1 |
|
5.2.3 总结
集合接口和集合类在
jdk5.0
时都修改为带泛型的结构在实例化集合类时,可以指定具体的泛型类型
指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(例如方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型
- 例如
add(E e)
—>实例化以后:add(Integer e)
- 例如
注意:泛型的类型必须是类,不能是基本数据类型
1
2ArrayList<int> list = new ArrayList<>(); // 错误!
ArrayList<Integer> list = new ArrayList<>(); // 正确!如果实例化时没有指定泛型,默认类型为
Object
类型
5.3 自定义泛型结构
5.3.1 自定义泛型类和接口
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
<E1,E2,E3>
例如Map的源码
1
2
3public interface Map<K, V> {
// ...
}
泛型类的构造器如下:
public GenericClass(){}
。而下面是错误的:public GenericClass<E>(){}
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
泛型不同的引用不能相互赋值。
1
2
3
4// 下面的做法是错误的
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = null;
list1 = list2;泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
jdk1.7,泛型的简化操作:
ArrayList<Fruit> flist = new ArrayList<>();
(类型推断)泛型的指定中不能使用基本数据类型,可以使用包装类替换。
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
异常类不能是泛型的。
1
2
3
4// 错误
public class MyException<T> extends Exception{
// ...
}父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
- 子类不保留父类的泛型:按需实现
- 没有类型 擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
- 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
- 子类不保留父类的泛型:按需实现
1 | class Father<T1, T2> { |
1 | class Father<T1, T2> { |
代码示例
- 自定义泛型类
1 | // 自定义泛型类 |
- 两个子类
1 | public class SubOrder extends Order<Integer>{ // 不再是一个泛型类 |
1 | public class SubOrder1<T> extends Order<T>{ // 仍然是一个泛型类 |
- 测试方法
1 |
|
5.3.2 自定义泛型方法
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
1 | 泛型方法的格式: |
代码示例
1 | // 自定义泛型类 |
- 测试方法
1 |
|
5.4 泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>
并不是G<A>
的子类型!
比如:String是Object的子类,但是List<String>
并不是List<Object>
的子类。
代码示例
1 |
|
5.5 通配符
- 使用类型通配符:
?
- 比如:
List<?>
,Map<?,?>
,List<?>
是List<String>
、List<Object>
等各种泛型List的父类。
- 比如:
- 读取
List<?>
的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。 - 写入list中的元素时,不行。因为我们不知道
?
的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。
代码示例
1 |
|
6 IO流
6.1 File类
6.1.1 介绍
java.io.File
类:文件和文件目录路径的抽象表示形式,与平台无关- File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器
6.1.2 常用构造器
public File(String pathname)
:以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir
中存储。public File(String parent, String child)
:以parent为父路径,child为子路径创建File对象。public File(File parent, String child)
:根据一个父File对象和子文件路径创建File对象
路径分隔符
路径中的每级目录之间用一个路径分隔符隔开。
路径分隔符和系统有关:
- windows和DOS系统默认使用
\
来表示 - UNIX和URL使用
/
来表示
- windows和DOS系统默认使用
Java程序支持跨平台运行,因此路径分隔符要慎用。
为了解决这个隐患,File类提供了一个常量:
public static final String separator
。根据操作系统,动态的提供分隔符。1
2
3File file1 = new File("d:\\atguigu\\info.txt");
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
File file3 = new File("d:/atguigu");
代码示例
1 |
|
6.1.3 常用方法
① 获取功能
public String getAbsolutePath()
:获取绝对路径public String getPath()
:获取路径public String getName()
:获取名称public String getParent()
:获取上层文件目录路径。若无,返回nullpublic long length()
:获取文件长度(即:字节数)。不能获取目录的长度。public long lastModified()
:获取最后一次的修改时间,毫秒值public String[] list()
:获取指定目录下的所有文件或者文件目录的名称数组public File[] listFiles()
:获取指定目录下的所有文件或者文件目录的File数组
代码示例1
1 |
|
代码示例2
1 |
|
② 重命名功能
public boolean renameTo(File dest)
:把文件重命名为指定的文件路径
代码示例
1 |
|
执行后,当前路径的hello.txt
已经转移至E:\develop\study\
,且重命名为hi.txt
③ 判断功能
public boolean isDirectory()
:判断是否是文件目录public boolean isFile()
:判断是否是文件public boolean exists()
:判断是否存在public boolean canRead()
:判断是否可读public boolean canWrite()
:判断是否可写public boolean isHidden()
:判断是否隐藏
代码示例
1 |
|
④ 创建功能
public boolean createNewFile()
:创建文件。若文件存在,则不创建,返回falsepublic boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。public boolean mkdirs()
:创建文件目录。如果上层文件目录不存在,一并创建
⑤ 删除功能
public boolean delete()
:删除文件或者文件夹- 删除注意事项:Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
6.2 IO流原理及流的分类
6.2.1 概述
I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。
java.io
包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
- 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。硬盘 —> 内存
- 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。 内存 —> 硬盘
应该在程序(内存)的角度看IO流的方向。
6.2.2 流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流(包在节点流外层)
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream |
Reader |
输出流 | OutputStream |
Writer |
Java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的。
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀
6.2.3 流的体系
表格中第二行为节点流(或称为文件流),剩余的都为处理流。
6.2.4 节点流和处理流
- 节点流:直接从数据源或目的地读写数据,或称为文件流
- 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
6.2.5 InputStream & Reader
InputStream(典型实现:FileInputStream) 和 Reader(典型实现:FileReader) 是所有输入流的基类。
程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源。
6.2.6 OutputStream & Writer
OutputStream 和 Writer 也非常相似。
因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组,即以 String 对象作为参数。
FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter。
6.3 节点流(文件流)
6.3.1 字符流读取文件
Reader:输入字符流
int read()
:读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1int read(char[] cbuf)
:将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。int read(char[] cbuf,int off,int len)
:将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。public void close() throws IOException
:关闭此输入流并释放与该流关联的所有系统资源。
代码示例1
需求:在项目下创建一个文件hello.txt
,文件内容为hello world
,以字符流FileReader
读取该文件到内存中并输出到控制台中显示。
1 |
|
注意:
- 为了保证流资源一定可以被释放掉,需要使用tcf处理
- 读入的文件一定要存在,否则会报
FileNotFoundException
代码示例2
1 | // 对read()操作升级,使用read的重载方法 |
6.3.2 字符流写入文件
Writer:输出字符流
void write(int c)
:写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。void write(char[] cbuf)
:写入字符数组。void write(char[] cbuf,int off,int len)
:写入字符数组的某一部分。从off开始,写入len个字符void write(String str)
:写入字符串。void write(String str,int off,int len)
:写入字符串的某一部分。void flush()
:刷新该流的缓冲,则立即将它们写入预期目标。public void close() throws IOException
:关闭此输出流并释放与该流关联的所有系统资源
代码示例1
需求:从内存中写出数据到硬盘里的文件里。
1 |
|
注意:
- 输出的文件对应的file可以不存在
- 不存在的话会自动创建文件。
- 如果存在,且流使用的构造器是:
FileWriter(file, false)
或FileWriter(file)
,则对原有文件进行覆盖 - 如果存在,且流使用的构造器是:
FileWriter(file, true)
,则对原有文件进行内容追加
代码示例2
需求:读取hello.txt
的内容,并写入到hello1.txt
中
1 |
|
注意点
字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。
6.3.3 字节流读取和写入文件
InputStream:输入字节流
int read()
:从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。int read(byte[] b)
:从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。int read(byte[] b, int off, int len)
:将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。public void close() throws IOException
:关闭此输入流并释放与该流关联的所有系统资源。
OutputStream:输出字节流
void write(int b)
:将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。void write(byte[] b)
:将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。void write(byte[] b,int off,int len)
:将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。public void flush()throws IOException
:刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。public void close() throws IOException
:关闭此输出流并释放与该流关联的所有系统资源。
代码示例
需求:实现对图片的复制操作
1 |
|
6.4 处理流
6.4.1 缓冲流
① 概述
为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
1 | public |
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
BufferedInputStream
和BufferedOutputStream
,处理字节BufferedReader
和BufferedWriter
,处理字符
注意点
- 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。
- 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。
- 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流。
- 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。
- flush()方法的使用:手动将buffer中内容写入文件。
- 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。
② 代码示例
代码示例1
需求:利用缓冲流实现非文本文件的复制操作
1 | // 实现非文本文件的复制 |
代码示例2
需求:对一个大文件进行复制操作,实现缓冲流和节点流的读写速度对比
略
代码示例3
需求:利用缓冲流实现文本文件的复制操作
注意简写操作。
1 |
|
6.4.2 转换流
① 概述
转换流提供了在字节流和字符流之间的转换。
Java API提供了两个转换流,两者都属于字符流:
InputStreamReader
:将InputStream转换为ReaderOutputStreamWriter
:将Writer转换为OutputStream
字节流中的数据都是字符时,转成字符流操作更高效。 很多时候我们使用转换流来处理文乱码问题。
实现编码和解码(字节/字节数组 —> 字符数组/字符串)的功能。
InputStreamReader
:字符输入流- 实现将字节的输入流按指定字符集转换为字符的输入流。
- 需要和InputStream“套接”。
- 构造器:
public InputStreamReader(InputStream in)
public InputSreamReader(InputStream in, String charsetName)
OutputStreamWriter
:字符输出流- 实现将字符的输出流按指定字符集转换为字节的输出流。
- 需要和OutputStream“套接”。
- 构造器:
public OutputStreamWriter(OutputStream out)
public OutputSreamWriter(OutputStream out, String charsetName)
② 字符集
- 编码表的由来:计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
- 常见的编码表:
ASCII
:美国标准信息交换码。用一个字节的7位可以表示。ISO8859-1
:拉丁码表。欧洲码表用一个字节的8位表示。GB2312
:中国的中文编码表。最多两个字节编码所有字符GBK
:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码Unicode
:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。UTF-8
:变长的编码方式,可用1-4个字节来表示一个字符。
面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF- 8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。
- ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK
- Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已,并不是具体的编码方案。
③ 代码示例
代码示例1
需求:用字节流(通过字符转换流转换)读取一个txt文件,将读取的内容输出到控制台上
1 |
|
1 | hello world你好世界 // UTF-8 |
代码示例2
需求:用字符转换流复制文本文件,并更换编码方式。
1 |
|
6.4.3 标准输入输出流
System.in
和System.out
分别代表了系统标准的输入和输出设备- 默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream,字节输入流
System.out的类型是PrintStream,字节输出流,其是OutputStream的子类FilterOutputStream 的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
代码示例
需求:从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
思路:System.in(字节输入流) --> 转换流 --> BufferedReader的readLine()
1 | public static void main(String[] args) { |
6.4.4 打印流
略
6.4.5 数据流
略
6.4.6 对象流
① 概述
ObjectInputStream
和OjbectOutputSteam
:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
对象的序列化
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
- 序列化的好处在于可将任何实现了
Serializable
接口的对象转化为字节数据,使其在保存和传输时可被还原。 - 序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是JavaEE 平台的基础。
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出
NotSerializableException
异常Serializable
Externalizable
凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;
- serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
- 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议显式声明。
- 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
② 代码示例
代码示例1
需求:
- 序列化:将内存中的java对象保存到磁盘中
- 反序列化:将磁盘中的Java对象读入到内存中
- 注意写出一次,操作flush()一次
1 | // 序列化 |
代码示例2
需求:自定义类的序列化和反序列化
- Person类:
1 |
|
- 测试类
1 |
|
结果:
1 | 我爱北京天安门 |
注意:如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field的类也不能序列化。
6.4.7 随机存取文件流
① 概述
- RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针。
long getFilePointer()
:获取文件记录指针的当前位置void seek(long pos)
:将文件记录指针定位到 pos 位置
构造器:
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
- 创建 RandomAccessFile 类实例需要指定一个
mode
参数,该参数指定 RandomAccessFile 的访问模式:r
: 以只读方式打开rw
:打开以便读取和写入rwd
:打开以便读取和写入;同步文件内容的更新rws
:打开以便读取和写入;同步文件内容和元数据的更新
- 如果模式为只读r,则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。
- 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建,且默认情况下对原内容进行覆盖操作。
- 我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能,用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能,有兴趣的朋友们可以自己实现下。
② 代码示例
代码示例1
需求:复制图片
1 |
|
代码示例2
需求:给定一个字符文件,内容为abcdefghijklmn
,要求在abc
后插入(且不覆盖后面的字符)字符xyz
1 |
|
6.5 NIO
略,详见实用技术-Netty学习笔记
7 网络编程
略,详见微服务基础-SpringMVC学习笔记
8 反射机制
8.1 反射机制概述
8.1.1 概述
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助于
Reflection API
取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
8.1.2 与反射有关的API
java.lang.Class
:代表一个类java.lang.reflect.Method
:代表类的方法java.lang.reflect.Field
:代表类的成员变量java.lang.reflect.Constructor
:代表类的构造器
8.1.3 利用反射之前对Person类的操作
代码示例
1 | public class Person { |
1 | // 反射之前对于Person类的操作 |
8.1.4 利用反射对Person类操作
代码示例
1 | // 利用反射对Person类的操作 |
8.1.5 如何看待反射和封装性
- 通过直接new对象,或反射的方式都可以调用公共的结构,那么开发中到底用哪个?
回答:建议直接用new的方法。
- 反射机制与面向对象的封装性是不是矛盾的?
回答:不矛盾。
8.2 理解Class类并获取Class实例 *
8.2.1 Class类的理解
1.类的加载过程:
程序在javac.exe
命令后,会生成一个或多个字节码文件(.class
结尾),接着使用java.exe
命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,此过程就称为==类的加载==。加载到内存中的类,就成为==运行时类==,此运行时类就作为Class类的一个实例对象。
2.换句话说,Class的实例就对应一个运行时类。
3.加载到内存中的运行时类,会缓存一定时间,在此时间之内,我们可以利用不同的方式来获取此运行时类。该运行时类在内存中只有一个。
代码示例
1 | Class c1 = Object.class; |
8.2.2 获取Class实例的四种方法
- 调用运行时类的属性:
.class
- 通过运行时类的对象,调用
getClass()
方法 - 调用Class的静态方法:
forName(String classPath)
用的最多 - 使用类加载器:
ClassLoader
了解,用得少
代码实例
1 | // 获取Class实例的四种方式 |
8.3 类的加载与ClassLoader的理解
8.3.1 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题。
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
执行类构造器
()方法的过程。类构造器 ()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步
8.3.2 ClassLoader的理解
类加载器
类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在==堆==中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
ClassLoader
1 |
|
使用
ClassLoader
加载配置文件
1 | // 读取配置文件 |
8.4 创建运行时对象 *
8.4.1 newInstance()
此方法实质上是调用运行时类的空参构造器,要想此方法正确的创建运行时类的对象,要求:
- 运行时类必须提供空参构造器
- 空参构造器的访问权限必须得够,通常设置为
public
在javabean
中要求提供一个空参构造器,便于通过反射来创建运行时类对象,便于子类继承此运行时类时,默认调用super()
时,保证父类有此构造器。
代码演示
1 |
|
8.4.2 反射的动态性理解
1 | // 体会反射的动态性 |
8.5 获取运行时类的完整结构
8.5.1 代码准备
1 | public class Creature<T> implements Serializable { |
1 |
|
1 | public interface MyInterface { |
1 |
|
8.5.2 获取运行时类的属性结构和内部结构
getFields()
getDeclaredFields()
1 |
|
getModifiers()
getType()
getName()
1 | // 权限修饰符 数据类型 变量名 |