1. IDEA常用快捷键
- 快速生成main方法:psvm
- 快速生成System.out.println():sout
- 删除一行:ctrl+y
- 任何新增/新建/添加的快捷键:alt+insert
- Java程序切换:alt+右箭头/左箭头
- 窗口切换:alt+数字标号(打开/关闭)
- 提示方法参数:ctrl+p
- 注释
- 单行注释:选中+ctrl+/
- 多行注释:选中+ctrl+shift+/
- 定位方法/属性/变量:ctrl+光标点击
- 纠正错误:光标点中+alt+回车
2. 面向对象
2.1. final关键字
2.1.1. 修饰类
final修饰的类无法被继承
final class A{} //A类不能被其他类继承
2.1.2. 修饰方法
final修饰的方法无法被覆盖和重写
class A{
public final a(){} //方法a不能被覆盖和重写
}
2.1.3. 修饰变量
final修饰的变量只能赋一次值
final int k;
k = 100;
k = 200; //×,只能赋一次值
-
修饰引用(引用也属于变量)
final修饰的引用只能指向一个对象,不能更改,并且也不会被GC回收,直到方法执行结束
但是该引用指向的对象内的数据可以被修改
class Person{ int age; public Person(){} public Person(int age){ this.age = age; } } final Person p = nwe Person(30); p = new Person(30); //×,引用p只能指向一个对象 p = null //×,也无法将对象p指向空。只有在当前方法执行结束后才会被GC回收 p.age = 40; //√
-
修饰实例变量
-
final修饰的实例变量,系统不会赋默认值,必须手动赋值。
-
实例变量在什么时候赋默认值(初始化)?
在构造方法执行时赋默认值(new的时候)
final修饰的实例变量要赶在系统赋默认值之前手动赋值。
class User{ // 第一种赋值方法 fianl double height = 1.8; // 第二种赋值方法 fianl double weight; public User(){ this.weight = 80; } }
-
-
常量
-
final修饰的实例变量一般加static修饰。
-
static和final联合修饰的变量称为“常量”,常量名全部大写,每个单词之间下划线衔接。
public static final String COUNTRY = "China"
-
常量和静态变量
都是存储在方法区,并且都是在类加载时初始化;
区别是常量的值不能变。
-
常量一般都是公开的。
-
2.2. 抽象类和接口
2.2.1. 抽象类
-
概念
把类和类之间的共同特征提取出来形成的就是抽象类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZRcHpKRN-1644842724107)(…/001-JavaSE课堂笔记+思维导图/09-JavaSE进阶每章课堂画图/01-面向对象/001-抽象类的理解.png)]
-
语法
[修饰符列表] abstract class 类名{ 类体; }
-
性质
-
抽象类属于引用数据类型。
-
抽象类是无法实例化、无法创建对象的,是用来被子类继承的。
-
final和abstract是对立的,不能联合使用。
-
抽象类的子类可以是抽象类。
-
抽象类可以有构造方法,用于供子类使用。
-
-
抽象方法:没有实现的方法。
-
语法:没有方法体,以分号结尾;用abstract修饰
public abstract void dosome();
-
抽象类中可以有或没有抽象方法、非抽象方法。
但抽象方法必须出现在抽象类中。
-
非抽象类继承抽象类,必须将抽象方法实现(覆盖/重写)
-
-
Java语言中没有方法体的方法都是抽象方法?
错,Object类中很多方法都没有方法体,例如:
// 该方法调用了c++写的动态链接库程序 // 修饰列表中没有abstract,有一个native,表示调用JVM本地程序。 public native int hashcode();
2.2.2. 接口
-
语法
[修饰符列表] interface 接口名 { 常量或抽象方法; // 接口内只能有这两部分内容 //接口中抽象方法定义时public abstract可以省略; //接口中常量定义时Public static final可以省路 }
-
性质
-
接口属于引用数据类型
-
接口是完全抽象的(抽象类是半抽象),特殊的抽象类
-
接口中所有元素都是public修饰
-
类和类之间叫做继承,类和接口之间叫做实现,用implements关键字完成
-
接口支持多继承,一个接口可以继承多个接口;一个类也可以实现多个接口
interface A{} interface B{} interface C extends A,B{} class D extends A,B{}
-
类与接口间的多态与类间的多态行为一致,参考多态的向下转型
-
接口之间没有继承关系也可以进行强转(向下转型必须出现在类相互继承的情况),但运行时很可能会出现classCastException异常。
(不用过多细想,记住每次强转时使用instanceof判断就行)
-
-
-
在开发中的作用(类似多态在开发中的作用):解耦合
面向接口编程,可以降低程序的耦合度,提高程序的扩展力,符合OCP原则。
接口的使用离不开多态。
2.2.3. 抽象类和接口
-
子类实现接口(继承抽象类)时,必须将其所有抽象方法都实现。
-
继承和实现都存在时,extends关键字在前,implements关键字在后。
class 子类名 extends 父类名 implements 接口名{}
-
区别
- 抽象类时半抽象的,接口时完全抽象的
- 抽象类中有构造方法,接口中没有构造方法
- 接口和接口之间支持多继承,类和类之间只能单继承
- 一个类可以同时实现多个接口,只能继承一个抽象类(单继承)
- 接口中值允许出现常量和抽象方法
2.3. package和import
2.3.1. package(包)
-
作用
- 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
- 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
-
语法
package 包名;
package语句只能出现在Java源代码的第一行
-
编译
javac - d . xxx.java
运行
java 完整类名(带包名)
-
包名的命名规范
公司域名倒序+项目名+模块名+功能名
2.3.2. import
-
什么时候使用
A类中使用B类,若A类和B类不在同一包下,必须使用import
【**Java.lang.***下的类不需要import导入】
-
怎么使用
import语句只能出现在package语句之下,class声明语句之上
2.4. 访问控制权限
- private私有:可以修饰属性、方法
只能在本类中访问
- protected受保护:可以修饰属性、方法
只能在本类、同包、子类中访问
- public公开:可以修饰属性、方法、类、接口
在任何位置都能访问
- 默认:可以修饰属性、方法、类、接口
只能在本类,以及同包下访问
访问控制修饰符 | 本类 | 同包 | 子类 | 任意位置 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
默认 | √ | √ | × | × |
private | √ | × | × | × |
public > protected > 默认 > private
2.5. Object类
2.5.1. 常用方法
-
什么是API
应用程序编程接口。(Application Program Interface)
整个JDK的类库就是一个javase的API。
每一个API都会配置一套API帮助文档。
SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。) -
常用方法
protected Object clone() // 负责对象克隆的。
int hashCode() // 获取对象哈希值的一个方法。
boolean equals(Object obj) // 判断两个对象是否相等
String toString() // 将对象转换成字符串形式
protected void finalize() // 垃圾回收器负责调用的方法【JDK 9后过时】
2.5.2. String toString()方法
- 作用:返回对象的字符串表示形式。
- System.out.println(引用); 这里会自动调用“引用”的toString()方法。
- 以后所有类的toString()方法是需要重写的。
重写规则,越简单越明了就好。
2.5.3. boolean equals(Object obj)方法
-
作用:判断两个对象是否相等
-
Object中的equals方法比较的是两个对象的内存地址(与“==”作用一样),我们应该比较内容,所以需要重写。
重写规则:自己定,主要看是什么和什么相等时表示两个对象相等。
-
基本数据类型判断是否相等用“==”;引用数据类型判断是否相等用equals()方法
-
判断两个字符串是否相等,不能使用==,要调用字符串对象的equals方法。
String类已经重写了equals()方法
2.5.4. protected void finalize()方法【JDK9后过时】
-
作用: 垃圾回收器(GC)负责调用的方法,不需要手动调用
-
源代码
protected void finalize() throws Throwable { }
-
执行时机:当一个Java对象即将被垃圾回收器回收的时候。
2.5.5. int hashCode()方法
-
返回的哈希码是一个对象的内存地址经过哈希算法转换的值
-
源代码
public native int hashCode();
不是抽象方法,带有native关键字,底层调用c++程序
2.6. 内部类
内部类的分类
- 静态内部类:类似与静态变量
- 实例内部类:类似于实例变量
- 局部内部类:类似于局部变量
- 匿名内部类属于局部内部类的一种
class A{
//静态内部类
static class B{}
//实力内部类
class C{}
public void m{
int i;
//局部内部类
class D{}
}
}
2.6.1. 匿名内部类
interface Compute{
int sum(int a, int b);
}
class MyMath{
public void mySum(Compute c, int x, int y){
int retValue = c.sum(x,y);
System.out.prinln(x + "+" + y + "=" + retValue);
}
}
public class test{
public static void main(String[] args){
MyMath mm = new MyMath();
//mm.myMath(匿名内部类,200,300)
//new Coumpute(){}中的{}表示对接口的实现
mm.mySum(new Compute(){
public int sum(int a, int b){
return a+b;
}
}, 200, 300) // 200+300=500
}
}
匿名内部类没有名字,因此不能重复使用,且可读性差
3. 数组
- 基本语法
- Java语言中的数组属于引用数据类型,不属于基本数据类型
- 数组长度不可变 ,所有数组对象都有length属性,用来获取数组中元素个数
- 数组中可以存储基本数据类型,也可以存储引用数据类型,但数组中元素类型要统一
- 内存结构
- 数组不能直接存储Java对象,实际上存储的是对象的引用。
- 数组对象存储在堆中
- 数组中元素内存地址是连续的,数组中首元素的内存地址就是整个数组的内存地址
- 数组中每个元素占用的空间大小相同
-
优点:检索效率高。
知道首元素的内存地址,通过一个数学表达式,就可以快速计算出某个下标位置上元素的内存地址,直接通过内存地址定位,效率非常高。
-
缺点:
-
随机增删效率较低,数组无法存储大数据量。
数组最后一个元素的增删效率很高,不受影响
-
不能存储大数据量,因为在内存空间中很难找到一块特别大的连续的存储空间
-
3.1. 一维数组
3.1.1. 初始化
- 静态初始化:
int[] arr = {1,2,3,4};
Object[] objs = {new Object(), new Object(), new Object()};
- 动态初始化:
int[] arr = new int[4]; // 4个长度,每个元素默认值0
Object[] objs = new Object[4]; // 4个长度,每个元素默认值null
3.1.2. 数组扩容
-
原理:新建一个大容量数组,再将小容量数组中元素拷贝进去。
-
涉及到数组拷贝,因此效率较低
3.1.3. 数组拷贝
-
源代码
// System.arraycopy(源数组,源数组下标,目标数组,目标数组下标,拷贝长度) System.arraycopy(Object src, int srcPos, Object dest, int destPos, int lenght)
-
内存图
3.2. 二维数组
二维数组是一个特殊的一位数组,其每个元素是一个一维数组
3.2.1. 初始化
-
静态初始化
int[][] arr = { {1,2,34}, {54,4,34,3}, {2,34,4,5} }; Object[][] arr = { {new Object(),new Object()}, {new Object(),new Object()}, {new Object(),new Object(),new Object()} };
-
动态初始化
int[][] arr = new int[3][4]; Object[][] arr = new Object[4][4]; Animal[][] arr = new Animal[3][4]; // Person类型数组,里面可以存储Person类型对象,以及Person类型的子类型都可以。 Person[][] arr = new Person[2][2];
3.3. 数组工具类
java.util.Arrays 是一个工具类。
4. 常用类
4.1. String类
4.1.1. 存储原理
- 在Java中随便使用双引号(“”)括起来的都是String对象,并且字符串一旦创建是不可变的。
- 在JDK中,由于字符串使用频繁,为了执行效率,字符串都直接存储在方法区的常量池中。GC不会释放常量。
- 字符串的比较必须使用equals方法。
- String已经重写了toString()和equals()方法。
举例1:
// 前两行代码中,底层实际创建了三个String对象,
// 分别是直接存储在方法区的常量池中的“abcdef”、“xy”和”abcdefxy“
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
String s3 = new String("xy");
内存图:
举例2:
public class User{
private int id;
private String name;
public User(int id, String name){
this.id = id;
this.name = name;
}
······
}
User user = new User(110, "张三");
内存图:
举例3:
// "hello"是存储在方法去的字符串常量池中
// 所以这个"hello"不会新建,因为这个对象已经存在了
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true,内存地址相同
String x = new String("xyz");
String y = new String("xyz");
System.out.println(x == y)//false,内存地址不同
System.out.println(x.equals(y)); // true,String类中已经重写了equals方法
内存图:
举例4:
// 一共创建了3对象
// 方法区字符串常量池中有1个:"hello"
// 堆内存中有2个String对象
String s1 = new String("hello");
String s2 = new String("hello");
4.1.2. 构造方法
String s = “abc”;
String s = new String(“abc”);
String s = new String(byte数组);
String s = new String(byte数组, 起始下标, 长度);
String s = new String(char数组);
String s = new String(char数组, 起始下标, 长度);
4.1.3. 常用方法
-
// 返回当前字符串长度 int length();
-
// 判断当前字符串是否为空字符串,null会空指针异常 boolean isEmpty();
-
//返回指定索引处char值 char charArt(int index);
-
//从第一个索引的值开始比较,按字典顺序,前后相等为0,前小后大为-1.前大后小为1 int compareTo(String anotherString);
-
//判断d字符串是否包含传入的字符串 boolean contains(CharSequence s);
-
// 判断当前字符串是否以传入的字符串结尾 boolean endsWith(String suffix); // 判断当前字符串是否以传入的字符串开始 boolean startsWith(String suffix);
-
// 判断两个字符串是否相等,并忽略大小写 boolean equalsIgnoreCase(String anotherString)
-
// 将当前字符串转换为byte数组 byte[] getByte(); // 将当前字符串转换为Char数组 char[] toCharArray()
-
// 返回传入字符串在当前字符串中第一次出现处的索引 int indexOf(String str); // 返回传入字符串在当前字符串中最后一次出现处的索引 int lastIndexOf(String str);
-
// 用新的字符/文本替换字符串中所有匹配的旧字符/文本,并返回替换后的新字符串。 String repalce(char oldChar, char newChar); String repalce(CharSequence oldText, CharSequence newText);
-
// 将当前字符串以regex为分隔符进行拆分,返回拆分后的字符串数组 String[] split(String regex);
-
// 将当前字符串以beginIndex为起始下标截取 String substring(int beginIndex); // 将当前字符串以beginIndex为起始下标,以endIndex-1为结束下标截取 String substring(int beginIndex,int endIndex);
-
// 将当前字符串中所有字符转换为小写 String toLowerCase(); // 将当前字符串中所有字符转换为大写 String toUperCase();
-
// 去除字符串中前后空白(中间空白不能去除) String trim();
-
// Sting中唯一的静态方法 // 将c非字符串转换为字符串 static String valueOf(Object o)
4.2. StringBuffer/StringBuilder
Java中每一次字符串拼接都会产生新的字符串,造成空间浪费
StringBuffer/StringBuilder类创建字符串缓冲区,用于大量进行字符串拼接的操作。
4.2.1. 基本语法
// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
StringBuffer sb = new StringBuffer(capacity:100);
sb.append(100);
sb.append(true);
sb.append("hello");
System.out.println(sb); //100truehello
4.2.2. StringBuffer和StringBuilder的区别
StringBuffer中的方法都有synchronized关键字修饰。表示stringBuffer在多线程环境下运行是安全的。
StringBuilder没有该关键字在多线程环境下运行是不安全的
4.3. 包装类
Java为8种基本类型对应准备了8种包装类,这8种包装类属于引用数据类型。
八种包装类的类名是什么?
Byte
Short
Integer
Long
Float
Double
Boolean
Character
4.3.1.装箱和拆箱
- 自动装箱和自动拆箱
//Java种将[-128,127]间所有包装对象提前创建好,放到一个方法区的“整数型常量池”中
// 目的是使用这个区间的数据不需要new了,直接从整数型常量池中取出来
Integer x = 100; // x里面并不是保存100,保存的是100这个对象的内存地址。
Integer y = 100;
System.out.println(x == y); // true,x中保存的内存地址与y中一样
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false
4.3.2. Integer类
-
内存结构
-
常用方法
// 将字符串转换为int类型 static int parseInt(String s);
// 将十进制转换为二进制字符串 static String toBinaryString(int i); // 将十进制转换为十六进制字符串 static String toHexString(int i); // 将十进制转换为八进制字符串 static String toOctalString(int i);
// 将int类型转换为Integer static Integer valueOf(int i); // 将String类型转换为Integer static Integer valueOf(String s);
-
String int Integer类型互换
4.4. 日期类
java.util.Date类
4.1.1. 常用方法
-
构造方法
import java.util.Date; public class Demo02Date { public static void main(String[] args) { demo03(); } /* long getTime() 把日期转换为毫秒值(相当于System.currentTimeMillis()方法) 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。 */ private static void demo03() { Date date = new Date(); long time = date.getTime(); System.out.println(time);//3742777636267 } /* Date类的带参数构造方法 Date(long date) :传递毫秒值,把毫秒值转换为Date日期 */ private static void demo02() { Date date = new Date(0L); System.out.println(date);// Thu Jan 01 08:00:00 CST 1970 date = new Date(3742767540068L); System.out.println(date);// Sun Aug 08 09:39:00 CST 2088 } /* Date类的空参数构造方法 Date() 获取当前系统的日期和时间 */ private static void demo01() { Date date = new Date(); System.out.println(date);//Sun Aug 08 12:23:03 CST 2088 } }
-
日期格式化:Date --> String
// 日期格式要准确 SimpleDateFormat sdf = new SimpleDate("yyyy-MM-dd HH:mm:ss SSS"); String s = sdf.format(new Date()); //yyyy-MM-dd HH:mm:ss SSS
-
String --> Date
SimpleDateFormat sdf = new SimpleDate("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse("2008-08-08 08:08:08");
-
获取毫秒数
// 获取去1970年1月1日到此时得总毫秒数数 long begin = System.currentTimeMillis(); // 获取昨天此时的时间 Date d = new Date(begin - 1000 * 60 * 60 * 24); // //yyyy-MM-dd HH:mm:ss SSS
4.5. 数字类
4.5.1. 格式化
java.text.DecimalFormat类
// 表示加入千分位,保留两个小数。
DecimalFormat df = new DecimalFormat(pattern: "###,###.##");
// 表示加入千分位,保留4个小数,不够补0
DecimalFormat df = new DecimalFormat(pattern: "###,###.0000");
4.5.2. 高精度
BigDecimal类,常用于处理财务数据
4.6. 随机数
4.6.1. 产生一个int类型随机数
Random r = new Random();
int i = r.nextInt();
4.6.2. 产生某个范围之内的int类型随机数
Random r = new Random();
int i = r.nextInt(101); // 产生[0-100]的随机数。
4.7. 枚举
4.7.1. 基本语法
enum 枚举类型名{
枚举值1,枚举值2,枚举值3;
}
4.7.2. 性质
- 枚举是一种引用数据类型。
- 枚举编译之后也是class文件
- 当一个方法执行结果超过两种情况,并且是一枚一枚可以列举出来的时候,建议返回值类型设计为枚举类型。
5. 异常处理
5.1. 异常的基本概念
-
控制台出现的异常信息由JVM打印
-
java中异常的作用是:增强程序健壮性。
-
java中异常以类和对象的形式存在。
5.2. 异常的分类
- 异常主要分为:错误、编译时异常(受控异常)、运行期异常(非受控异常)
- 错误:如果应用程序出现了Error,那么将无法恢复,只能重新启动应用程序,最典型的
Error 的异常是:OutOfMemoryError - 受控异常:出现了这种异常必须显示的处理,不显示处理java 程序将无法编译通过
- 非受控异常:此种异常可以不用显示的处理,例如被0 除异常,java 没有要求我们一定要
处理
- 错误:如果应用程序出现了Error,那么将无法恢复,只能重新启动应用程序,最典型的
- 编译时异常因为什么而得名?
因为编译时异常必须**在编译(编写)阶段预先处理,**如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。
因为异常的发生就是new异常对象。 - 编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
5.3. 对异常的处理
-
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果:终止java程序的执行。
-
第二种方式:使用try…catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。-
catch后面的小括号中的类型可以是具体的异常类型。也可以是该异常类型的父类型
-
catch可以写多个,多个catch必须遵从异常从小到大。
-
JDK8以后catch括号中可以用或逻辑
catch(FileNotFoundException | ArithmaticException | NullPointerException e)
-
5.4. 异常对象的常用方法
5.4.1. getMessage()方法
获取异常简单的描述信息
String msg = exception.getMessage();
5.4.2. printStackTrace()方法
打印异常追踪的堆栈信息
exception.printStackTrace();
5.5. finally子句
-
finally语句必须和try一起出现(可以没有catch),不能单独编写
-
finally子句中的代码是最后执行的,并且一定会执行,即使try语句块中代码出现了异常、即使try语句中正常且有return
【注意】如果退出JVM,finally就不会执行
-
执行顺序
先执行try,再执行finally,最后执行return
public static void main(String[] args){ try{ System.out.println("try..."); return; } finally{ System.out.println("finally..."); } }
-
特例
public static void main(String[] args){ int result = m(); System.out.println(result); } public static int m(){ int i = 100; try{ return i; }finally { i++; } }
输出结果为100
因为Java规则不能破坏:方法体中的代码必须遵循自上而下顺序依次逐行执行;
return语句一旦执行,整个方法必须结束。
上代码反编译结果:
public static int m(){ int i = 100; int j = i; i++; return j; }
5.6. 自定义异常
-
第一步:编写一个类继承Exception或者RuntimeException
第二步:提供两个构造方法,一个无参数,一个带有String参数
6. 集合
6.1. 集合概述
6.1.1. 什么是集合
- 集合是一个容器,是一个载体,可以一次容纳多个对象。
- 在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
6.1.2. 集合的性质
- 集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,
集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
list.add(100); //自动装箱Integer
- 注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是“引用”(对象的内存地址)。
-
在java中每一个不同的集合,底层会对应不同的数据结构。
往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。
-
所有的集合类和集合接口都在java.util包下。
6.1.3. 集合的继承结构
-
在java中集合分为两大类:
-
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection; -
一类是以键值对的方式存储元素
以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;
-
-
集合的继承结构图–Collection部分
-
集合的继承接口图–Map部分
6.2. Collection和Iterator
6.2.1. Collection中的常用方法
-
// 向集合中添加元素 boolean add(Object e);
-
// 获取集合中元素个数 int size();
-
// 清空集合 void clear();
-
// 如果此集合包含指定的元素,则返回 true。 boolean contains(Object o);
方法原理:
-
// 删除集合中指定元素 boolean remove(Object o);
-
// 判断集合是否为空 boolean isEmpty();
-
// 返回包含此集合中所有元素的数组。 Object[] toArray();
-
// 获取集合的迭代器对象 Iterator iterator();
当集合结构发生改变(如增删)时,迭代器必须重新获取
6.2.2. Iterator迭代器(在所有collection中通用)
-
常用方法
// 如果目标仍有元素可以迭代,则返回true boolean hasNext(); // 返回迭代的下一个元素 Object next();
next()方法的返回类型必须是Object
-
迭代原理
-
迭代集合的原理
6.3. List接口
6.3.1. 特有的常用方法
-
初始化
// 默认容量(10) List l1 = new ArrayList(); // 初始化容量为20 List l2 = new ArrayList(20); // 将HashSet集合转换为List集合 Collection c = new HashSet(); c.add(100); c.add(200); List l3 = new ArrayList(c);
-
// 在列表指定索引出插入元素 // 效率比较低,使用较少 void add(int index, Object element);
-
// 修改列表指定索引处元素 Object set(int index, Object element);
-
// 根据列表索引获取元素 Object get(int index);
-
// 获取指定对象在列表中第一次出现的索引 int indexOf(Object o); //获取指定对象在列表中最后一次出现的索引 int lastIndexOf(Object o);
-
// 删除列表中指定索引处的元素 Object remove(int index);
6.3.2. ArrayList 实现类
- ArrayList集合底层是Object类型的数组,初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素时,初始化容量为10)
- ArrayList元素超出容量时会自动扩容为原来的1.5倍,扩容效率较低
6.3.3. LinkedList 实现类
-
链表
-
单向链表
-
双向链表
-
-
Linked List是双向链表结构
LinkedList内存地址
-
链表数据结构随即增删效率较高,检索效率较低。
在空间内存上,内存地址不连续。
6.3.4. Vector 实现类
-
Vector底层是数组,初始化容量为10,扩容为原容量的2倍。
-
Vector中所有方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低。
-
将ArrayList转换为线程安全的方法:java.util.Collections集合工具类
Collections.synchronizedList(list);
6.4. JDk新特性
6.4.1. 泛型(JDK5.0之后)
-
使用泛型好处
- 集合中存储的元素统一
- 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”。
-
泛型的缺点
- 集合中存储的元素缺乏多样性
-
自定义泛型
public class Genetic<E>{ public E dosone(){} } Genetic<String> g1 = new Generic<>(); String d1 = g1.dosome(); // 返回String类型 Genetic<Integer> g2 = new Generic<>(); Integer d2 = g2.dosome(); // 返回Integer类型 Genetic g3 = new Generic(); Object d3 = g3.dosome(); // 返回Object类型
6.4.2. 钻石表达式(JDK8之后)
List<String> list = new ArrayList<>();
类型自动推断!
6.4.3. foreach(JDK5.0以后)
foreach为增强for循环
-
语法
for(元素类型 变量名 : 数组或集合){}
6.5. Map 接口
6.5.1. 常用方法
-
// 向Map集合中添加键值对 V put(K key, V value);
-
// 通过key获取value V get(Object key);
-
// 获取Map集合中键值对的个数 int size();
-
// 清空Map集合 void clear();
-
// 判断Map中是否包含某个key boolean containsKey(Object key); // 判断Map中是否包含某个value boolean containsValue(Object value);
contains方法底层都是调用equals方法,所以自定义的类型要重写equals方法
-
// 获取Map集合所有的key,返回的是一个Set集合 Set<K> keySet(); // 获取Map集合中所有value,返回的是一个Collection集合 Collection<V> values();
-
// 判断Mao集合是否为空 boolean isEmpty();
-
// 通过key删除键值对 V remove(Object key);
-
// 将Map集合转换成Set集合,返回的Set集合中每个元素为“key=value” // Map.Entry是一个Map类中的静态内部类Entry Set<Map.Entry<K,V>> entrySet();
6.5.2. 遍历方式
-
第一种方法,拿到所有key值,通过遍历key值来遍历value
Set<Integer> keys = map.keySet(); // 迭代器方式 Iterator<Integer> it = keys.iterator(); while(it.hasNext()){ Integer ket = it.next(); String value = map.get(key); } // foreach 方式 for(Integer key : keys){ String value = map.get(key); }
-
第二种方法,通过调用Set<Map.Entry<K,V>> entrySet()方法,把Map集合转换成Set集合(其元素类型为Map.Entry)
遍历Set集合,每次取一个Node
Set<Map.Entry<Integer,String>> set = map.entrySet(); // 迭代器方式 Iterator<Map.Entry<Integer,String>> it = set.iterator(); while(it.hasNext()){ Map.Entry<Integer,String> node = it.next(); Integer key = node.getKey(); String value = node.getValue(); } // foreach方式 for(Map.Entry<Integer,String> node : set){ Integer key = node.getKey(); String value = node.getValue(); }
-
foreach 方式效率较高,适合大数据量
6.5.3. HashMap
-
哈希表
-
哈希表是一个数组和单向链表的结合体。
哈希表是一个一维数组,每一个元素是一个单向链表。
-
数据结构
-
-
HashMap的默认初始化容量为16(自定义是必须为2的倍数),默认加载因子为0.75(容量达到75%时开始扩容),扩容后为原容量的2倍。
-
放在HashMap和HashSet的key中的元素需要同时重写HashCode()和equals()方法。
-
在JDK8之后,为了提高检索效率
如果HashMap的哈希表中单向链表中元素超过8时,会将单向链表数据结构变为红黑树数据结构。
如果红黑树上的节点数量小于6,会重新把红黑树变成单向链表数据结构。
-
HashMap和Hashtable的区别
- HashMap:
初始化容量16,扩容2倍。
非线程安全
key和value可以为null。 - Hashtable:
初始化容量11,扩容2倍+1
线程安全
key和value都不能是null。
- HashMap:
6.5.4. Properties 类
-
性质
- Properties继承Hashtable,其key和value值都是String类型。
- 被称为属性类对象
- 线程安全
-
常用方法
-
// 调用Hashtable的方法put Object setProperties(String key, String value);
-
// 根据key值取value String getProperties(String key);
-
6.5.5. TreeMap集合
-
TreeSet集合底层是一个TreeMap;TreeMap底层是一个平衡二叉树
-
TreeSet集合中的元素无序不可重复,但可以按照元素的大小顺序自动排序。
-
TreeMap的key或者TreeSet集合中的元素要想排序,有两种实现方式:
第一种:实现java.lang.Comparable接口。
第二种:单独编写一个比较器Comparator接口,在构造TreeMap或TreeSet集合时传一个比较器对象 。 -
自平衡二叉树
6.5.6. Collections 集合工具类
-
synchronizedList()方法,将非线程安全集合变为线程安全
// ArrayList不是线程安全的。 List<String> list new ArrayList<>(); // 将其变成线程安全的 Collecions.synchronizedList(list);
-
sort方法(要求集合中元素实现Comparable接口。),对集合中元素进行排序。
7. IO流
7.1. Java流概述
7.1.1. 流的分类
- 按流的方向:输入流(读)和输出流(写)(以内存为参照物)
- 按读取数据的方式:字节流(任何类型文件)和字符流(只能读取纯文本文件)
7.1.2. 四大家族
- java.io.InputStream 字节输入流
java.io,OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
四大家族的首领都是抽象类。(abstract class)
-
所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。
-
所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。
输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。 -
java.io包下需要掌握的流有16个:
文件专属: java.io.FileInputStream(掌握) java.io.FileOutputStream(掌握) java.io.FileReader java.io.FileWriter 转换流:(将字节流转换成字符流) java.io.InputStreamReader java.io.OutputStreamWriter 缓冲流专属: java.io.BufferedReader java.io.BufferedWriter java.io.BufferedInputStream java.io.BufferedOutputStream 数据流专属: java.io.DataInputStream java.io.DataOutputStream 标准输出流: java.io.PrintWriter java.io.PrintStream(掌握) 对象专属流: java.io.ObjectInputStream(掌握) java.io.ObjectOutputStream(掌握)
7.2. 文件流
7.2.1. FileInputStream
-
读取文件
FileInputStream fis = null; try{ fis = new FileInputStream("temptfile"); byte[] bytes = new byte[fis.available()]; int readCount = 0; while((readCount = fis.read(bytes)) != -1){ System.out.println(new String(bytes, 0, readCount)); } } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException e){ e.printStackTrace(); } finally{ if(fis != null){ try{ fis.close(); } catch(IOException e){ e.printStackTrace(); } } }
-
其他方法
// 返回流中剩余没有读取的字节数 int available(); // 跳过几个字节不读取 long skip(long n);
7.2.2. FileOutputStream
-
写入文件
FileOutputStream fos = null; try{ // 以追加的方式在文件末尾写入。不会清空原文件内容。 // 不加boolean append参数会将原文件清空,再重新写入 fos = new FileInputStream("temptfile", true); String s = "abcdef"; // 将字符串转换成byte数组 byte[] bytes = s.getBytes(); fos.wrtite(bytes); // 将byte数组的一部分写入 fos.wrtite(bytes, 0, 2); // ab // 写完之后,最后一定要刷新 fos.flush(); } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException e){ e.printStackTrace(); } finally{ if(fos != null){ try{ fos.close(); } catch(IOException e){ e.printStackTrace(); } } }
7.2.3. FileReader
文件字符输入流,只能读取文件(char字符)。读取文本内容时比较方便。
读取文件方式与FileInputStream方法一样,只是读取为char数组
7.2.4. FileWriter
文件字符输出流,只能写入文件(char字符)。
写入文件方式与FileOutputStream方法一样,只是写入为char数组或字符串。
7.3. 缓冲流
7.3.1. BufferedReader
带有缓冲区的字符输入流,使用时不需要自定义char数组,自带缓冲
FileInputStream fis = null;
try{
fis = new FileInputStream("temptfile");
// 将字节流文件转换为字符流文件
InputStreamReader reader = new InputStreamReader(fis);
// FileReader是一个节点流,BufferedReader是包装流/处理流(负责外部包装的称为包装流)
BufferedReader br = new BufferedReader(reader);
String s = null;
// 读一行
while((s = br.readLine()) != null){
System.out.println(s);
}
// 对于包装流,只需要关闭最外层流就行,里面的节点流会自动关闭
br.close();
} catch(FileNotFoundException e){
e.printStackTrace();
} catch(IOException e){
e.printStackTrace();
} finally{
if(br != null){
try{
br.close();
} catch(IOException e){
e.printStackTrace();
}
}
}
7.3.2. BufferedWriter
FileOutputStream fos = null;
try{
fos = new FileOutputStream("tempfile", true);
OutStreamWriter writer = new OutStreamWriter(fos);
BufferedWriter br = new BufferedWriter(writer);
String s = "abcdef";
br.writer(s);
br.flush();
br.close();
} catch(FileNotFoundException e){
e.printStackTrace();
} catch(IOException e){
e.printStackTrace();
} finally{
if(br != null){
try{
br.close();
} catch(IOException e){
e.printStackTrace();
}
}
}
7.4. 标准输出流
7.4.1. PrintStream
-
改变输出方向到指定文件
// 指定一个文件 PrintStream out = new PrintStream(new FileOutputStream("temptfile", true)); // 改变输出方向 System.setOut(out); System.out.println("此时输出不再打印到控制台,而是写入指定文件中");
7.3. java.io.File类
7.3.1. 特点
-
File类不能完成文件的读和写
-
File对象是文件和目录路径名的抽象表现形式
如C:\Drivers和C:\Drivers\Readme.txt都是一个File对象
7.3.2. 常用方法
// 创建一个File对象
File f1 = new File("D:\\file");
// 判断是否存在
boolean f1.exists();
// 以文件形式创建D:\\file
f1.createNewFile();
// 以目录形式创建D:\\file
f1.mkdir();
// 构建多重目录
File f2 = new File("D:\\file\\a\\b");
f2.mkdirs();
// 获取父路径
String s1 = f2.getParent(); // D:\\file\\a
// 获取绝对路径
String s2 = f2.getAbsolutePath();
// 获取文件名
String s3 = f1.getName();
// 获取文件最后一次修改时间。结果是从1970年到现在的毫秒数
String s4 = f1。lastModified();
// 获取当前目录下所有的子目录
File[] files = f1.listFiles();
7.4. 对象流
7.4.1. 对象的序列化和反序列化
-
参与序列化和反序列化的接口必须实现Serializable接口
-
Serializable接口中没有代码,起到标志作用,给Java虚拟机参考后自动生成序列化版本号
-
如果自动生成序列化版本号,修改类的内容序列化版本号会改变,再次编译会出错
-
建议对需要序列化和反序列化的类手动写序列化版本号
private static final long serialVersionUID = 1L;
-
-
代码实列
-
单个对象
class Student implete Serializavle{ } // 对象序列化 Student s = new Student(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students")); oos.writeObject(s); oos.flush(); oos.close(); // 对象反序列化 ObjectInputStream ois= new ObjectInputStream(new FileInputStream("students")); Object obj = ois.readObject(); ois.close();
-
多个对象
class User implete Serializavle{ } // 对象序列化 List<User> userList = new ArrayList(); userList.add(new User()); userList.add(new User()); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users")); oos.writeObject(userList); oos.flush(); oos.close(); // 对象反序列化 ObjectInputStream ois= new ObjectInputStream(new FileInputStream("users")); Object obj = ois.readObject(); if(obj instanceof List) // true; List<User> userList = (List<User>)obj; ois.close();
-
7.4.2. transient 关键字
修饰对象的成员变量,表示该变量不参加序列化
8. 多线程
8.1. 概述
8.1.1. 进程和线程
- 进程是一个应用程序(软件)
- 线程是一个进程中的执行场景/执行单元
- 一个进程可以启动多个线程
8.1.2. 线程中内存的关系
-
一个线程一个栈,栈是独立的,堆和方法区是多线程共享的
8.2. 线程的创建和实现
8.2.1. start()和run()方法
-
start()方法:启动一个分支线程,在JVM中开辟一个新的栈空间
启动成功的线程会自动调用run()方法,并且run()方法在分支栈的栈底部(压栈)
-
run()方法在分支栈的栈底部,main()方法在主栈的栈底部,run()和main()是平级的
-
如果新建分支对象,没有调用start()方法,而是调用run()方法。不会启动线程,不会分配新的分支栈
8.2.2. 实现方法
-
第一种方法
编写一个类,直接继承java.lang.Thread,重写run方法
public static void main(String[] args){ // 新建一个分支线程对象 MyThread t = new MyThread(); // 启动线程 t.start(); // 这段程序运行在主线程中(主栈) for(int i=0; i<1000; i++){ System.out.println(i); } } //定义线程类 class NyThread extends Thread{ @Override public void run(){ // 这段程序运行在分支线程中(分支栈) for(int i=0; i<1000; i++){ System.out.println(i); } } }
-
第二种方法(更灵活)
编写一个类,实现java.lang.Runnable接口,实现run方法
public static void main(String[] args){ // 创建一个可运行的对象 MyRunnable r = new MyRunnable(); // 将可运行的对象封装成一个线程对象 Tread t = new Thread(r); // 启动线程 t.start(); // 这段程序运行在主线程中(主栈) for(int i=0; i<1000; i++){ System.out.println(i); } } // 这并不是一个线程类,是一个可运行的类。它还不是一个线程 class MyRunnable implements Runnable{ @Override public void run(){ for(int i=0; i<1000; i++){ System.out.println(i); } } }
-
第三种方式(JDK8新特性)
实现Callable接口。
优点:可以获取到线程的执行结果。
这种方式的缺点:在获取t线程执行结果的时候,当前线程受阻塞,效率较低。// 第一步:创建一个“未来任务类”对象。 // 参数非常重要,需要给一个Callable接口实现类对象。 FutureTask task = new FutureTask(new Callable() { @Override public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值 // 线程执行一个任务,执行之后可能会有一个执行结果 // 模拟执行 System.out.println("call method begin"); Thread.sleep(1000 * 10); System.out.println("call method end!"); int a = 100; int b = 200; return a + b; //自动装箱(300结果变成Integer) } }); // 创建线程对象 Thread t = new Thread(task); // 启动线程 t.start(); // 这里是main方法,这是在主线程中。 // 在主线程中,怎么获取t线程的返回结果? // get()方法的执行会导致“当前线程阻塞” Object obj = task.get(); System.out.println("线程执行结果:" + obj); // main方法这里的程序要想执行必须等待get()方法的结束 // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果 // 另一个线程执行是需要时间的。 System.out.println("hello world!");
8.3. 线程的生命周期
8.4. 线程的方法
8.4.1. 常用方法
-
获取当前线程对象
// 在main方法中就是main Tread t = Tread.currentTread();
-
获取线程名字
// 默认为Tread-0、Tread-1······· String s = 线程对象.getName();
-
修改线程名字
线程对象.setName();
8.4.2. sleep方法
使当前线程进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用
// 参数为毫秒
static void sleep(long millis);
-
中断sleep方法
线程对象.interruput();
中断线程的睡眠,依靠java的异常处理机制
8.4.3. 合理终止线程的执行
public static void main(String[] args){
// 创建一个可运行的对象
MyRunnable r = new MyRunnable();
// 将可运行的对象封装成一个线程对象
Tread t = new Thread(r);
// 启动线程
t.start();
// 这段程序运行在主线程中(主栈)
for(int i=0; i<1000; i++){
System.out.println(i);
}
// 终止线程
t.run = false;
}
// 这并不是一个线程类,是一个可运行的类。它还不是一个线程
class MyRunnable implements Runnable{
@Override
public void run(){
// 做一个布尔标记
boolean run = true;
if(run){
for(int i=0; i<1000; i++){
System.out.println(i);
}
}
else{
// 中止当前线程
return;
}
}
}
8.5. 线程安全
8.5.1. 存在线程安全的条件
- 存在线程安全问题的条件
- 多线程并发
- 有共享数据
- 共享数据有修改行为
- 解决方法:线程排队执行(不能并发),称为线程同步机制
- 线程同步会牺牲一部分效率
8.5.2. 线程同步
-
异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,
多线程并发(效率较高。)
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
两个线程之间发生了等待关系,这就是同步编程模型。
效率较低。线程排队执行。 -
语法
synchronized(多线程共享的对象){ // 线程同步代码块 }
-
如果共享的对象就是this,并且需要同步的代码是整个方法体,可以将synchronized使用在实例方法上,表示锁this,如
public synchronized void withDraw(double money){}
-
在静态方法上使用synchronized,表示使用类锁(不管创建多少个对象,都共享一个类锁),用来保护静态变量的安全
8.5.3. Java中三大变量的线程安全问题
-
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
-
局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。 -
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
-
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
8.5.4. 解决线程安全方法
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。
8.6.守护线程
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程。
守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
一直在那里看着,没到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。
设置守护线程的方法:
t.setDaemon(true);
t.start();
8.7. 定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
代码实现
// 创建定时器对象
Timer timer = new Timer();
//Timer timer = new Timer(true); //守护线程的方式
// 指定定时任务
//timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-03-14 09:34:30");
//timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
// 每年执行一次。
//timer.schedule(new LogTimerTask() , firstTime, 1000 * 60 * 60 * 24 * 365);
//匿名内部类方式
timer.schedule(new TimerTask(){
@Override
public void run() {
// code....
}
} , firstTime, 1000 * 10);
// 编写一个定时任务类
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {
@Override
public void run() {
// 编写你需要执行的任务就行了。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ":成功完成了一次数据备份!");
}
}
8.8. wait和notify方法
wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,这两个方式是Object类中自带的。
8.8.1. wait()方法
Object o = new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态,并且释放之前占有的o对象的锁。
8.8.2. notify()方法
Object o = new Object();
o.notify();
表示:
唤醒正在o对象上等待的线程。不会释放之前占有的o对象的锁。
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程。
8.8.3. 生产者和消费者模式
9. 反射机制
什么是反射机制:可以操作字节码文件
作用:让程序更灵活
**相关类:*java.lang.reflect.
重要类:
java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
java.lang.Class:
public class User{
// Field
int no;
// Constructor
public User(){}
public User(int no){
his.no = no;
}
// Method
public void setNo(int no){
this.no = no;
public int getNo(){
return no;
}
}
字节码内存图:
9.1. 反射类Class
-
获取java.lang.Class实例三种方式
第一种:Class c = Class.forName(“完整类名带包名”);
第二种:Class c = 对象.getClass();
第三种:Class c = 任何类型.class; -
通过反射实例化对象
Class c = Class.forName("完整类名带包名"); // newInstance()调用无参构造方法,需要保证类的无参构造方法存在 Object obj = c.newInstance();
优点:在不改变Java源代码的基础上,做到对不同对象的实例化。符合OCP开闭原则
只执行类中的静态代码块,可以使用Class.forName(“完整类名带包名”);
-
获取类路径下文件的绝对路径
// Thread.currentThread() 当前线程对象 // getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。 // getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。 String path = Thread.currentThread().getContextClassLoader() .getResource("类路径下(src目录)开始").getPath(); // 这种方式获取文件绝对路径是通用的。
-
文件以流的形式直接返回
InputStream reader = Thread.currentThread().getContextClassLoader() ,getResourceAsStream("类路径下开始");
-
资源绑定器
资源绑定器只能绑定properties文件,并且这个文件必须在类路径下
再写路径的时候,路径后面的扩展名不能写
ResourceBundle bundle = ResourceBundle.getBundle("properties文件路径(扩展名不写)")
9.2. 反射属性Field
-
获取类的属性Field的方法
// 获取类的公共属性 Field[] fields = 对象名.getFields(); // 获取类的所有属性 Field[] fs = 对象名.getDeclaredFields(); // 根据类的属性名来获取属性 Field field = 对象名.getDeclareField("属性名"); // 获取属性名 String name = fields[0].getName(); // 获取属性的修饰符列表 int i = fields[0].getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号 // 将“代号”转换成“字符串” String modifierString = Modifier.toString(i); // 获取属性的类型 Class fieldType = fields[0].getType(); // 获取属性的类型名 String fName = fieldType.getName(); // 获取属性的简单类型名 String fn = fieldType.getSimpleName();
-
通过反射对象访问Java对象的属性
class Student{ private int no = 1111; } Class studentClass = Class.forName("Student"); Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法) // 获取no属性(根据属性的名称来获取Field) Field noFiled = studentClass.getDeclaredField("no"); // 打破封装 // 这样设置完之后,在外部也是可以访问private的。 nameField.setAccessible(true); // 给obj对象(Student对象)的no属性赋值 /* 虽然使用了反射机制,但是三要素还是缺一不可: 要素1:obj对象 要素2:no属性 要素3:2222值 */ noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222 // 读取属性的值 // 两个要素:获取obj对象的no属性的值。 System.out.println(noFiled.get(obj));
9.3. 反射方法Method
-
可变长参数
public static void m(int... args){} m(); m(1); m(1,2); m([1,2,3]);
- 可变长度参数参数个数是0~n个
- 可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有一个
- 可变长度参数可以当作一个数组来看待
-
反射Method
class UserService{ public void a(){} private int b(){ return 1; } } // 获取类 Class userServiceClass = Class.forName("UserService"); // 获取所有的Method(包括私有的!) Method[] methods = userServiceClass.getDeclaredMethods(); // 遍历Method for(Method method : methods){ // 获取修饰符列表 System.out.println(Modifier.toString(method.getModifiers())); // 获取方法的返回值类型 System.out.println(method.getReturnType().getSimpleName()); // 获取方法名 System.out.println(method.getName()); // 方法的修饰符列表(一个方法的参数可能会有多个。) Class[] parameterTypes = method.getParameterTypes(); for(Class parameterType : parameterTypes){ System.out.println(parameterType.getSimpleName()); } }
-
通过反射对象调用一个对象的方法
public class UserService {
/**
* 登录方法
* @param name 用户名
* @param password 密码
* @return true表示登录成功,false表示登录失败!
*/
public boolean login(String name,String password){
if("admin".equals(name) && "123".equals(password)){
return true;
}
return false;
}
// 可能还有一个同名login方法
// java中怎么区分一个方法,依靠方法名和参数列表。
public void login(int i){
}
//退出系统的方法
public void logout(){
System.out.println("系统已经安全退出!");
}
}
Class userServiceClass = Class.forName("UserService");
// 创建对象
Object obj = userServiceClass.newInstance();
// 获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
//Method loginMethod = userServiceClass.getDeclaredMethod("login", int.class);
// 调用方法
// 调用方法有几个要素? 也需要4要素。
// 反射机制中最最最最最重要的一个方法,必须记住。
/*
四要素:
loginMethod方法
obj对象
"admin","123" 实参
retValue 返回值
*/
Object retValue = loginMethod.invoke(obj, "admin","123123");
System.out.println(retValue);
9.4. 反射构造方法Constructor
-
使用反射机制创建对象
// 使用反射机制怎么创建对象呢? Class c = Class.forName("完整类名带包名"); // 调用无参数构造方法 Object obj = c.newInstance(); System.out.println(obj); // 调用有参数的构造方法 // 第一步:先获取到这个有参数的构造方法 Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class); // 第二步:调用构造方法new对象 Object newObj = con.newInstance(110, "jackson", "1990-10-11", true); System.out.println(newObj); // 获取无参数构造方法 Constructor con2 = c.getDeclaredConstructor(); Object newObj2 = con2.newInstance(); System.out.println(newObj2);
-
获取父类和父接口
// String举例 Class stringClass = Class.forName("java.lang.String"); // 获取String的父类 Class superClass = stringClass.getSuperclass(); System.out.println(superClass.getName()); // 获取String类实现的所有接口(一个类可以实现多个接口。) Class[] interfaces = stringClass.getInterfaces(); for(Class in : interfaces){ System.out.println(in.getName()); }
10. 注解
10.1. 定义和使用
-
注解Annotation是一种引用数据类型,编译后生成xxx.class文件
注解只在编译阶段起作用,和运行阶段无关
-
自定义语法格式
[修饰符列表] @interface 注解类型名{ }
举例:
public @interface MyAnnotation { /** * 我们通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性。 * 看着像1个方法,但实际上我们称之为属性name。 * @return */ String name(); /* 颜色属性 */ String color(); /* 年龄属性 */ int age() default 25; //属性指定默认值 } // 如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。) //@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值), //如果注解只有一个属性,并且属性名是value,属性名可以省略 @MyAnnotation(name = "zhangsan", color = "红色") public void doSome(){ }
注解中属性的的类型可以是:
byte short int long float double boolean char String Class 枚举类型
以及以上每种的数组形式
如果数组只有一个元素,大括号可以省略
-
使用
第一:注解使用时的语法格式是:
@注解类型名第二:注解可以出现在类上、属性上、方法上、变量上等…注解还可以出现在注解类型上。
-
JDK内置注解
java.lang包下的注释类型:
- 用 @Deprecated 注释的程序元素,表示标注的元素已过时
不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。 - @Override 表示一个方法声明打算重写超类中的另一个方法声明。
- 用 @Deprecated 注释的程序元素,表示标注的元素已过时
10.2. 元注解
-
什么是元注解
用来标注“注解类型”的“注解”,称为元注解。
-
常见的元注解有哪些?
Target
Retention -
关于Target注解:
这是一个元注解,用来标注“注解类型”的“注解”
这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。 @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) 表示该注解可以出现在:构造方法上,字段上,局部变量上,方法上,包上,类上...
-
关于Retention注解:
这是一个元注解,用来标注“注解类型”的“注解”
这个Retention注解用来标注“被标注的注解”最终保存在哪里。@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。 @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。 @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。
源代码:
//元注解 public @interface Retention { //属性 RetentionPolicy value(); } public enum RetentionPolicy { SOURCE, CLASS, RUNTIME } //@Retention(value=RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation{}
10.3. 反射注解
-
反射获取注解对象及其属性
// 获取这个类 Class c = Class.forName("MyAnnotationTest"); // 判断类上面是否有@MyAnnotation if(c.isAnnotationPresent(MyAnnotation.class)){ // 获取该注解对象 MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class); // 获取注解对象的属性 String value = myAnnotation.value(); System.out.println(value); }