1.类加载器
类的加载:
概述:当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类的初始化
加载:
- 就是指将class文件读入内存,并为之创建一个Class对象
- 任何类被使用时系统都会建立一个Class对象
连接:
- 验证:是否有正确的内部结构,并和其他类协调一致
- 准备:负责为类的静态成员分配内存,并设置默认初始值
- 解析:将类的二进制数据中的符号引用替换为直接引用
初始化:像默认初始化,显示初始化,构造初始化等
类初始化的时机
1.创建类的实例
2.访问类的静态变量,或者为静态变量赋值
3.调用类的静态方法
4.使用反射方式来强调创建某个类或接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接使用java.exe命令来运行某个主类
类加载器
概述:类加载器负责将.class文件加载到内存中,并为之生成对应的Class对象,虽然我们不需要关系类加载器机制,但是了解这个机制我们就能更好的理解程序的运行
类加载器的组成:
- Bootstrap ClassLoader:根类加载器
- 也被成为引导类加载器,负责java核心类的加载,比如System,String等,在JDK中JRE的lib目录下的rt.jar文件中
- Extension ClassLoader:扩展类加载器
- 负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下的ext目录
- System ClassLoader:系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
2.反射
概述:简单的说就是通过class文件对象(也就是Class类的对象),去使用该文件中的成员变量,构造方法和成员方法
Class类
获取class文件对象的三种方式
1.类名.class
2.对象.getClass()
3.Class:forName("类名"):这里的类名是包括包名的全称类名(推荐使用)
代码示例:
import java.util.Date;
public class Demo10 {
public static void main(String[] args) throws ClassNotFoundException {
String str = "123";
Class cls1 = str.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true
System.out.println(cls1.isPrimitive());//false,判断是否为基本数据类型的Class实例对象的方法
System.out.println(int.class == Integer.class);//false
System.out.println(int.class == Integer.TYPE);//true,TYPE是基本数据类型包装类和void所独有的静态字段,代表着基本数据类型的字节码
int[] array = {1, 3, 4};
System.out.println(array.getClass().isPrimitive());//false
System.out.println(array.getClass().isArray());//true,判断Class是否为数组的方法
}
}
通过反射获取成员变量,构造方法,成员方法:
1.获取成员变量: Field
- public Field[] getFields():获取所有公共变量
- public Field[] getDeclaredFields():获取所有变量
- public Field getField(String name):获取单个指定的变量(不能为私有的变量)
- public Field getDeclaredField(String name):获取单个变量(可以是私有变量)
- 字段对象.set(对象, “该字段的值”):设置某个字段(成员变量)的值
2.获取构造方法: Constructor
- public Constructor[] getConstructors():获得所有的公共构造方法
- public Constructor[] getDeclaredConstructors():获取所有的构造方法
- public Constructor
getConstructor(Class<?>... parameterTypes):获取单个公共的构造方法,参数表示的是你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象 - public Constructor
getDeclaredConstructor(Class<?>... parameterTypes):获取单个构造方法(包括私有构造方法),参数表示的是你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象 - public T newInstance(Object... instargs):使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
3.获取成员方法: Method
- public Method[] getMethods():获取自己的包括父亲的成员方法
- public Method[] getDeclaredMethods():获取自己的方法
- public Method getMethod(String name, Class<?>... parameterTypes):获取单个方法(不可获取私有的方法)
- public Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取单个方法(可获得私有的方法)
- public Object invoke(Object obj, Object... args):返回值是object接受,第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数
代码示例1(通过反射获取和设置成员变量):
//cn.luyi.demo1.Person.java
package cn.luyi.demo1;
public class Person {
private String name;
private int age;
public String city;
public Person(){};
private Person(String name){
this.name = name;
};
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", city=" + city + "]";
}
}
//Demo1
public class Demo1 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("cn.luyi.demo1.Person");
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//获取单个公共变量
Field cityField = c.getField("city");
cityField.set(obj, "广州");
System.out.println(obj);//Person [name=null, age=0, city=广州]
//获取单个私有变量
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj, "卢一");
System.out.println(obj);//Person [name=卢一, age=0, city=广州]
}
}
代码示例2(通过反射获取构造方法):
//cn.luyi.demo1.Person.java
package cn.luyi.demo1;
public class Person {
private String name;
private int age;
public String city;
public Person(){};
private Person(String name){
this.name = name;
};
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", city=" + city + "]";
}
}
//Demo2.java
import java.lang.reflect.Constructor;
public class Demo2 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.luyi.demo1.Person");
//获得无参数构造方法
Constructor con = c.getConstructor();
//通过这个无参构造创建一个新实例
Object obj = con.newInstance();
System.out.println(obj);//Person [name=null, age=0, city=null]
//获得带参数的构造方法
Constructor con2 = c.getConstructor(String.class, int.class);
//通过这个有参构造创建一个新实例
Object obj2 = con2.newInstance("luyi", 19);
System.out.println(obj2);//Person [name=luyi, age=19, city=null]
//获得带参数的私有构造方法
Constructor con3 = c.getDeclaredConstructor(String.class);
//设置反射的对象在使用的时候取消java语言访问检查,不设置会抛出IllegalAccessExcetion异常
con3.setAccessible(true);
//通过这个私有带参构造方法获得一个新实例
Object obj3 = con3.newInstance("huangyi");
System.out.println(obj3);//Person [name=huangyi, age=0, city=null]
}
}
代码案例3(通过反射获取成员方法):
//Student.java
public class Student {
public Student(){
};
public void show(){
System.out.println("I am a student");
}
private int sum(int n){
int plus = 0;
for(int i = 0; i <= n; i ++){
plus += i;
}
return plus;
}
}
//Demo3.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Demo3 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.luyi.demo1.Student");
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//获取无参数的成员方法
Method method1 = c.getMethod("show");
method1.invoke(obj);
//获取私有带参的成员方法
Method method2 = c.getDeclaredMethod("sum", int.class);
method2.setAccessible(true);
//获得返回值
Integer result = (Integer)method2.invoke(obj, 10);
System.out.println(result);
}
}
反射案例
案例1:通过反射越过泛型检查
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 怎么给一个ArrayList<Integer>对象添加一个字符串数据呢?
*/
public class Demo4 {
public static void main(String[] args) throws Exception {
ArrayList<Integer> array = new ArrayList<Integer>();
Class c = array.getClass();
//ArrayList的字节码文件传的这个参数的类型为Object类型,而不是Integer类型
Method m = c.getMethod("add", Object.class);
m.invoke(array, "Hello");
System.out.println(array);
}
}
3.动态代理
代理概述:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象
动态代理概述:在程序运行过程中产生的这个动态代理对象,而我们的动态代理其实就是通过反射来生产一个代理的
动态代理的实现:在java中的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandle接口,通过使用这个类和接口就可以生成动态代理对象,JDK提供的代理只能针对接口来做代理,我们有更强大的代理cglib可以不仅仅针对接口来做
Proxy类中的方法创建动态代理对象:
- public static Object newProxyInstance(ClassLoader loader,Class<?>[] interface,InvocationHandler h),最终会调用InvocationHandler的方法
- InvocationHandler接口重写的方法:Object invoke(Object proxy, Method method,Object[] args)
具体代码实现(通过动态代理给增删改查方法添加权限功能和日志记录功能):
//UserDao.java
public interface UserDao {
abstract public void add();
abstract public void delete();
abstract public void update();
abstract public void find();
}
//UserDaoImpl.java
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加功能");
}
@Override
public void delete() {
System.out.println("删除功能");
}
@Override
public void update() {
System.out.println("修改功能");
}
@Override
public void find() {
System.out.println("查找功能");
}
}
//MyInvocationHandle.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandle implements InvocationHandler {
private Object target;//目标对象
public MyInvocationHandle(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("权限校验");
Object result = method.invoke(target, args);
System.out.println("日志记录");
return result;
}
}
//Text.java
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
UserDao ud = new UserDaoImpl();
//对ud对象做一个代理
MyInvocationHandle handle = new MyInvocationHandle(ud);
UserDao proxy = (UserDao)Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(),handle);
proxy.add();
proxy.delete();
proxy.update();
proxy.find();
}
}
4.枚举(Enum)
为什么要有枚举
1.问题:要定义星期几或性别的变量,该怎么定义呢?假设用1-7分别表示星期一到星期天,但有人可能就会把星期天用0表示
2.枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错,枚举可以让编译器在编译时就控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标
枚举类的注意事项
1.定义枚举类要用关键字enum
2.所有枚举类都是Enum的子类
3.枚举类的第一行必须是枚举项,最后一个枚举项后面的分号是可以省略的,但是如果枚举类有其他的东西,这个枚举类不能省略
4.枚举类可以有构造器,但必须是private修饰的,它默认也是private修饰的
5.枚举类可以有抽象方法,但是枚举项必须将重写该方法
6.枚举可以用在switch语句中
枚举的基本应用
定义:public enum 枚举名称{枚举元素列表}
使用:
public class Demo8 {
public static void main(String[] args) {
//使用枚举
WeekDay weekDay = WeekDay.Fri;
System.out.println(weekDay);//Fri
//枚举对象的成员方法
System.out.println(weekDay.name());//Fri
System.out.println(weekDay.ordinal());//5,在枚举的内容中是第几个(从0开始)
//枚举类的静态方法
System.out.println(WeekDay.valueOf("Sun"));//Sun,把字符串Sun变成对应的枚举元素
System.out.println(WeekDay.values().length);//7,得到了有所有枚举元素的数组,然后求这个数组的长度
}
//定义了一个枚举类,列举出星期天到星期六
public enum WeekDay{
Sun, Mon, Tue, Wed, Thi, Fri, Sat
}
}
实现带有构造方法的枚举
public enum WeekDay{
//如果想要使用带有参数的构造方法,则需要在各个枚举元素后面加个小括号然后传参
Sun() , Mon(1), Tue("abc"), Wed, Thi, Fri, Sat;
//构造方法必须放在枚举元素列表之后,而且方法必须私有
private WeekDay(){
System.out.println("执行了无参数的构造方法");
};
private WeekDay(int day){
System.out.println("执行了带有整数参数的构造方法");
};
private WeekDay(String day){
System.out.println("执行了带有字符串类型参数的构造方法");
}
}
实现带有抽象方法的枚举
public class Demo9 {
public static void main(String[] args) {
TrafficeLamp t = TrafficeLamp.RED;
System.out.println(t);//RED
System.out.println(t.nextLamp());//GREEN
System.out.println(t.getTime());//30
System.out.println(t.nextLamp().nextLamp().getTime());//3
}
//实现了带有抽象方法以及带有参数的构造方法的枚举
public enum TrafficeLamp{
//这里的每个枚举元素后面跟个参数,就相当于new一个对象后面跟个参数的构造方法
//后面加个大括号调用抽象方法,就相当于前面new出来的对象调用了这个抽象方法
RED(30){
@Override
public TrafficeLamp nextLamp(){
return GREEN;
}
},
GREEN(30){
@Override
public TrafficeLamp nextLamp(){
return YELLOW;
}
},
YELLOW(3){
@Override
public TrafficeLamp nextLamp(){
return RED;
}
};
public abstract TrafficeLamp nextLamp();
private int time;
private TrafficeLamp(int time){
this.time = time;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
}
}
枚举实现单例模式
保证整个系统中的一个类只有一个对象的实例,实现这种功能的方式就是单例模式
当枚举只有一个成员时,就可以作为一种单例的实现方式了
5.方法引用的基本入门(简化Lambda表达式,java8之后才有)
在某些场景之下,Lambda表达式要做的事情,其实在另外一个类里已经写过了,那么此时如果通过Lambda表达式重复编写相同的代码,就是浪费,那么,如何才能复用已经存在的方法逻辑呢?
接下来我们来看一个案例:
//Cook.java
public class Cook {
//定义了一个做菜的静态方法
public static void makeFood(String food){
System.out.println("将" + food + "作成可口的食物");
}
}
//Sitter.java
//定义一个保姆接口
public interface Sitter {
void work(String food);
}
//Demo3.java
public class Demo3 {
public static void main(String[] args) {
//直接使用Lambda表达式
hireSitter(food -> System.out.println("将" + food + "作成可口的食物"));
//使用方法引用,引用了在Cook类里的makeFood方法
hireSitter(Cook::makeFood);
}
//雇佣一个保姆,并且让她去做菜
public static void hireSitter(Sitter sitter){
sitter.work("白菜");
}
}
通过以上这个例子,我们也可以大致了解到方法引用的格式有一个为:类名::这个类里的方法
接下来我们再具体看看方法引用的两种常见格式
通过类名称引用静态方法的格式:类名称::静态方法
通过对象名引用成员方法的格式:对象名::成员方法
6.Stream流式思想(java8之后才有)
stream流式操作可以帮我们节省很多的代码,接下来我们就来认识一下stream流吧
java8当中的“流”其实就是Stream接口的对象
JDK提供了一个流接口:java.util.stream.Stream
如何获取流?
1.根据集合获取流:集合名称.stream()
2.根据数组获取流:Stream.of(数组名称)
代码示例:
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("luyi");
list.add("luer");
list.add("lusan");
Stream<String> streamA = list.stream();
String[] array = {"lusi", "luwu", "luliu"};
Stream<String> streamB = Stream.of(array);
}
}
Stream流的map映射方法
获取流之后,可以使用映射方法:map(用于转换的Lambda表达式)
映射:就是将一个对象转换成为另一个对象,把老对象映射到新对象上
使用映射把字符串类型的数据转换为数字类型的数据的示例:
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("100");
list.add("200");
list.add("300");
//与list.stream().map(Integer::parseInt)等价
/*Stream<Integer> stream = list.stream().map((String str) -> {
return Integer.parseInt(str);
});*/
Stream<Integer> stream = list.stream().map(Integer::parseInt);
System.out.println(stream);
}
}
Stream流的filter过滤方法
如果希望对流当中的元素进行过滤,可以使用过滤方法
格式:filter(能产生boolean结构的Lambda)
过滤掉不是luyi的人的示例
public class Demo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("luyi");
list.add("luer");
list.add("lusan");
Stream<String> streamA = list.stream().filter((String str) ->{
boolean b = "luyi".equals(str);
return b;
});//filter那部分代码等价于filter(str -> "luyi".equlas(str));
}
}
Stream流的forEach遍历方法
如果希望在流当中进行元素的遍历操作,可以使用forEach方法
forEach(Lambda表达式):意思是,对流当中的每一个元素都要进行操作。参数Lambda表达式必须是一个能够消费一个参数,而且不会产生数据结果的Lambda
例如:
Lambda s->System.out.println(s);等价于
方法引用:System.out::println
forEach的使用:
import java.util.stream.Stream;
public class Demo4 {
public static void main(String[] args) {
String[] array = {"赵露思" , "赵丽颖", "赵四"};
//遍历Stream流打印数据
Stream.of(array).forEach((String str)->{
System.out.println(str);
});
//等价于forEach(s->System.out.println(s));
//也等价于forEach(System.out::println);
}
}
Stream流几个方法的综合使用:
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo4 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("赵露思,20");
list.add("赵丽颖,30");
list.add("赵四,40");
//过滤掉岁数小于20的人
list.stream().map(s -> s.split(",")[1]).map(s -> Integer.parseInt(s)).filter(n -> n > 20).forEach(System.out::println);
}
}
并发的Stream流:支持并发操作的流
流当中的元素如果特别多,那么只有一个人在逐一、挨个处理,肯定比较慢,费劲
如果对流当中的元素,使用多个人同时处理,这就是“并发”
那么如何才能获取“并发流”呢?
格式:.parallelStream()
注意事项:
1.使用并发流操作的时候,到底有几个人进行同时操作呢?不用管,JDK自己处理
2.只要正确使用,就不会出现多个人抢到同一个元素的情况
3.如果已经获取了一个普通的流,那么只要再调用一下parallel()方法也会变成并发流
示例代码:
import java.util.ArrayList;
public class Demo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for(int i = 1; i <= 100; i ++){
list.add("hello----" + i);
}
//这是只有一个人在做打印输出的操作
list.stream().forEach(System.out::println);
//这是多个人在做打印输出的操作
list.parallelStream().forEach(System.out::println);
//这是已经有了一个普通流,想转换为并发流
list.stream().parallel().forEach(System.out::println);
}
}
7.模块化(java9及以上版本才有)
模块化思想可以给我们带来的好处:
1.整体一整个,文件体积过大,如果只是用到了其中的一部分内容,就会浪费
2.精确控制package包级别的访问控制,只有导出的包,模块之外才可以访问,否则,只有模块内部自己访问
不同的IDE有不同的创建module-info.java文件的方法
这里讲一下Eclipse创建module-info.java的方法
如果你想在项目上建一个模块,那就在你的项目上进行一下操作:
右键你的项目名->找到Configure选项->点击它就会出现Create module-info.java的选项
模块的描述信息module-info.java文件的基本内容:
module 本模块的名称{
exports 导出的包名称;
requires 需要依赖的其他模块名称;
}