本篇文章的学习资源来自Java学习视频教程:Java核心技术(高阶)_华东师范大学_中国大学MOOC(慕课)
本篇文章的学习笔记即是对Java核心技术课程的总结,也是对自己学习的总结

文章目录

  • Java核心技术(高阶):深层原理
    • 第一章 Java语法糖
      • 1、语法糖(Syntax sugar)和环境设置
      • 2、语法糖(1)foreach和枚举
      • 3、语法糖(2)不定项参数和静态导入
      • 4、语法糖(3)自动拆箱和装箱、多异常并列、数值类型和优化
      • 5、语法糖(4)接口方法
      • 6、语法糖(5)try-with-resource和ResourceBundle文件加载
      • 7、语法糖(6)var类型和switch
    • 第二章 Java泛型
      • 1、泛型入门
      • 2、自定义泛型设计
      • 3、泛型类型限定
      • 4、泛型实现的本质和约束
      • 5、Java类型协变和逆变
    • 第三章 Java反射
      • 1、反射入门
      • 2、反射关键类
      • 3、反射应用
      • 4、编译器API
    • 第四章 Java代理
      • 1、代理模式和静态代理
      • 2、动态代理
      • 3、AOP编程
    • 第五章 Java注解
      • 1、注解入门
      • 2、Java预定义的普通注解
      • 3、自定义注解
      • 4、Java预定义的元注解
      • 5、注解的解析
      • 6、RUNTIME注解的实现本质
      • 7、注解的应用
    • 第六章 嵌套类
      • 1、嵌套类入门
      • 2、匿名内部类和局部内部类
      • 3、普通内部类和静态嵌套类
      • 4、嵌套类对比
      • 5、嵌套类应用
    • 第七章 Lambda表达式
      • 1、Lambda表达式入门
      • 2、函数式接口
      • 3、方法引用
      • 4、Lambda表达式应用
    • 第八章 Java Stream流
      • 1、流的概述
      • 2、流的创建
      • 3、流的转换
      • 4、Optional类型
      • 5、流的计算结果
      • 6、流的应用
    • 第九章 Java模块化
      • 1、Java模块化概述
      • 2、模块创建和运行
      • 3、模块信息文件
      • 4、服务
      • 5、Java模块化应用
    • 第十章 Java字节码
      • 1、Java字节码概述
      • 2、Java字节码文件构成
      • 3、Java字节码指令分类
      • 4、Java字节码操作
      • 5、Java字节码增强
      • 6、Java字节码混淆
      • 7、Java字节码总结和展望
    • 第十一章 Java类加载器
      • 1、Java类加载机制
      • 2、Java类双亲委托加载扩展
      • 3、自定义类加载路径
      • 4、自定义类加载器
      • 5、Java类加载器总结与展望
    • 第十二章 JVM内存管理
      • 1、JVM概述
      • 2、JVM内存分类
      • 3、JVM内存参数
      • 4、Java对象引用
      • 5、垃圾收集算法
      • 6、JVM堆内存参数和GC跟踪
    • 第十三章 Java运行管理
      • 1、Java运行管理概述
      • 2、OS层管理
      • 3、JDK管理工具
      • 4、可视化管理工具
      • 5、堆文件分析
      • 6、JMX
      • 7、Java运行安全
    • 第十四章:高阶总结
      • Spring

Java核心技术(高阶):深层原理

——学习Java的高级特性,理解和开发框架软件

导学

回归到Java深层次的原理特性

  • Java泛型和反射
  • Java代理和注解
  • 内部类
  • Lambda表达式和stream流处理
  • Java类加载机制和安全策略
  • Java模块化编程
  • Java字节码
  • JVM和内存管理

第一章 Java语法糖

1、语法糖(Syntax sugar)和环境设置
2、语法糖(1)foreach和枚举

foreach

优点:

  • 语法更简洁
  • 避免越界错误

缺点:

  • foreach只能只读操作,不能修改和删除元素
  • foreach遍历的时候,是不知道当前元素的具体位置索引
  • foreach只能正向遍历,不能反向遍历
  • foreach不能同时遍历2个集合
  • for和foreach性能接近

枚举类型

枚举变量:变量的取值只在一个有限的集合内

enum类型

  • enum关键字声明枚举类,且都是Enum的子类(但不需要写extends)
  • enum内部有多少个值,就有多少个实例对象
  • 不能直接new枚举类对象
  • 除了枚举内容,还可以添加属性/构造函数/方法
    • 构造函数只能是default或者private,内部调用
  • 枚举方法
    • 所有的enum类型都是Enum的子类,也继承了相应的方法
    • ordinal()返回枚举值所在的索引位置,从0开始
    • compareTo()比较两个枚举值的索引位置大小
    • toString()返回枚举值的字符串表示
    • valueOf()将字符串初始化为枚举对象
    • values()返回所有的枚举值

eg:

enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE;
}
Size s1 = Size.SMALL;
Size s2 = Size.SMALL;
Size s3 = Size.MEDIUM;
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false

**小结:**foreach和enum建议使用

3、语法糖(2)不定项参数和静态导入

不定项参数

  • 普通函数的形参列表是固定个数/类型/顺序
  • 不定项参数
    • 类型后面加3个点,如int…/double…/String…/
    • 可变参数,本质上是一个数组
    • 不定项参数的限制条件
      • 一个方法只能有一个不定项参数,且必须位于参数列表的最后
      • 重载的优先级规则1:固定参数的方法,比可变参数优先级更高
      • 重载的优先级规则1:调用语句,同时与两个带可变参数的方法 匹配,则报错

**静态导入:**import static导入一个类的静态方法和静态变量

**小结:**不定项参数:注意方法重载的优先级,建议少用;静态导入,注意“*”不滥用

4、语法糖(3)自动拆箱和装箱、多异常并列、数值类型和优化

自动装箱和拆箱

  • 简化基本类型和对象转换的写法

    • 装箱:基本类型的值被封装为一个包装类对象
    • 拆箱:一个包装类对象被拆开并获取相应的值
  • 自动装箱和拆箱的注意事项
    • 装箱和拆箱是编译器的工作,在class中已经添加转换。虚拟机没有自动装箱和拆箱的语句
    • ==:基本类型是内容相同,对象是指针是否相同(内存同一个区域)
    • 当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算
Integer a1 = 1000;
int a2 = 1000;
Integer a3 = 2000;
Long a4 = 2000L;
long a5 = 2000L;
System.out.println(1000==1000L);//基本数据类型比较,只需内容相同  true
System.out.println(a1 == a2);  //拆箱再进行数值比较    true
System.out.println(a3 == (a1 + a2));  //拆箱再进行数值比较    true
System.out.println(a4 == (a1 + a2));  //拆箱再进行数值比较    true
System.out.println(a5 == (a1 + a2));  //拆箱再进行数值比较    trueSystem.out.println(a3.equals(a1+a2)); //equals要求同类,且内容相同    true
System.out.println(a4.equals(a1+a2)); //equals要求同类,且内容相同    false
System.out.println(a4.equals((long) (a1+a2))); //equals要求同类,且值相同    true//System.out.println(a3 == a4); //不同类型不能比较

多异常并列

  • 多个异常并列在一个catch中

    • 多个异常之间不能有(直接/间接)继承关系
try{test();
}
catch(IOException | SQLException ex){//JDK7开始,支持一个catch写多个异常//异常处理
}

整数类型用二进制赋值

  • 避免二进制计算
  • byte/short/int/long
byte a1 = (byte) 0b00100001;
short a2 = (short) 0b1010000101000101;
int a3 = 0b10100001010001011010000101000101;
int a4 = 0b101;
int a5 = 0B101; //B可以大小写
long a6 = 0b1010000101000101101000010100010110100001010001011010000101000101L;final int[] s1 = { 0b00110001, 0b01100010, 0b11000100, 0b10000100 };

小结:多异常并列不建议使用,针对每一种类型异常单独处理

5、语法糖(4)接口方法
  • Java最初的设计中,接口的方法都是没有实现的、公开的
  • Java8推出接口的默认方法/静态方法(都带实现的),为Lambda表达式提供支持

接口的默认方法

  • 以default关键字标注,其他的定义和普通函数一样

    1. 默认方法不能重写Object中的方法
    2. 实现类可以继承/重写父接口的默认方法
    3. 接口可以继承/重写父接口的默认方法
    4. 当父类和父接口都有(同名同参数)默认方法,子类继承父类的默认方法
    5. 子类实现了2个接口(均有同名同参数的默认方法),那么编译失败,必须在子类中重写这个default方法
public default void move()
{System.out.println("I can move.");
}

接口的静态方法

  • 该静态方法属于本接口的,不属于子类/子接口

    • 接口的默认方法:可以传给后代的类/接口的;接口的静态方法:只属于当前接口,不会传给后代的类/接口
  • 子类(子接口)没有继承该静态方法,只能通过所在的接口名来调用

Java9接口的私有方法(带实现的)

  • 解决多个默认方法/静态方法的内容重复问题
  • 私有方法属于本接口,只在本接口内使用,不属于子类/子接口
  • 子类(子接口)没有继承该私有方法,也无法调用
  • 静态私有方法可以被静态/默认方法调用,非静态私有方法被默认方法调用

小结:接口的方法,建议少用。如有Lambda表达式需求,可使用

6、语法糖(5)try-with-resource和ResourceBundle文件加载

ResourceBundle

  • Java8及以前,ResourceBundle默认以ISO-8859-1方式加载Properties文件,需要利用native2ascii工具对文件进行转义
  • jdk9及以后,ResourcecBundle默认以UTF-8方式加载Properties文件。
    • jdk9及以后,已经删除native2ascii工具
    • 新的Properties文件直接以UTF-8保存
    • 已利用native2ascii工具转化后的文件,不受影响。即ResourceBundle若解析文件不是有效的UTF-8,则以ISO-8859-1方式加载
7、语法糖(6)var类型和switch

var

  • Java10推出var:局部变量推断

    • 避免信息冗余
    • 对齐了变量名
    • 更容易阅读
    • 本质上还是强类型语言,编译器负责推断类型,并写入字节码文件。因此推断后不能更改。
  • var的限制
    • 可以用在局部变量上,非类成员变量
    • 可以用在for/foreach循环中
    • 声明时必须初始化
    • 不能用在方法(形式)参数和返回类型
    • 大面积滥用会使代码整体阅读性变差
    • var只在编译时起作用,没有在字节码中引入新的内容,也没有专门的JVM指令处理var

建议:var变量只在局部变量中使用

switch

  • 支持的类型:byte/Byte,short/Short,int/Integer,char/Character,String,Enum
  • 不支持:long/float/double/boolean

第二章 Java泛型

1、泛型入门

泛型:Generic Programming,编写的代码可以被很多不同类型的对象所重用

  • 泛型类:ArrayList,HashSet,HashMap等
  • 泛型方法:Collections.binarySearch,Arrays.sort等
  • 泛型接口:List,Iterator等

泛型的本质:参数化类型,避免类型转换,代码可复用

  • 同类:

    • C++的模板(Template)
    • C#的泛型
2、自定义泛型设计

自定义泛型设计

  • 泛型类:整个类都被泛化,包括变量和方法
  • 泛型方法:方法被泛化,包括返回值和参数
  • 泛型接口:泛化子类方法

泛型类

  • 具有泛型变量的类

  • 在类名后用<T>代表引入类型

    • 多个字母表示多个引入类型,如<T,U>等
    • 引入类型可以修饰成员变量/局部变量/参数/返回值
    • 泛型类不需要关键字
    public class GenericTest<T> {private T lower;private T upper;public GenericTest(T lower, T upper) {this.lower = lower;this.upper = upper;}public T getLower() {return lower;}
    }
    

    泛型类调用

    GenericTest<Integer> v1=new GenericTest<>(1,2);
    

泛型方法

  • 具有泛型参数的方法
  • 该方法可在普通类/泛型类中
  • <T>在修饰符后,返回类型前
public static <T> T getMiddle(T... a) {return a[a.length / 2];
}

泛型方法调用

String s1=getMiddle("abc","def","ghi");

泛型接口

  • 和泛型类相似,在类名后加<T>
  • T用来指定方法返回值和参数
  • 实现接口时,指定类型
  • 泛型接口,T也可以再是一个泛型类(不推荐)
3、泛型类型限定

泛型

  • 编写的代码可以被很多不同类型的对象所重用
  • 特定场合下,需要对类型进行限定(使用某些特定方法)

泛型限定

  • <T extends Comparable>约定T必须是Comparable的子类

    • 注意:extend固定,后面可以多个,以&拼接,如<T extends Comparable & Serializable>
    • extends限定可以有多个接口,但只能有一个类,且类必须排第一位

泛型通配符

问题引出:Pair<Apple>和Pair<Fruit>之间是不存在任何关系的,也不能通用,为了弥补这个不足,引入泛型通配符

  • 上限界定符,Pair<? extends S>

    • Pair能接收的参数类型,是S自身或子类
    • 只能get,不能set,编译器只能保证出来的类型,但不保证放入的对象是什么类型
  • 下限界定符,Pair<? super S>
    • Pair能接收的类型参数,是S的自身或超类
    • 只能set,不能get。编译器保证放入的是S本身或超类,但不保证出来是什么具体类型
4、泛型实现的本质和约束

JVM里面没有泛型对象,而是采用类型擦除技术,只有普通的类和方法

类型擦除

  • 擦除泛型变量,替换为原始类型,无限定为Object,有限定则为第一个类型
  • 擦除泛型变量后,为了保证类型的安全性,需要自动进行类型转换。(这是编译器的工作)
  • 重载泛型方法翻译(自动桥方法)
5、Java类型协变和逆变

类型变化关系

  • A,B是类型,f(.)表示类型转换,–>表示继承关系,如A–>B,表示A继承于B
  • f(.)是协变的,如果A–>B,有f(A)–>f(B)
  • f(.)是逆变的,如果A–>B,有f(B)–>f(A)
  • f(.)是不变的,当上述两种都不成立,即f(A)和f(B)没有任何关系
  • f(.)是双变的,如果A–>B,有f(A)–>f(B)和f(B)–>f(A)

eg:

  • Java数组是协变的

    class A{}    //第一代
    class B extends A{}     //第二代
    class C extends B{}     //第三代B[] array1 = new B[1];
    array1[0] = new B();A[] array2 = array1;try {array2[0] = new A(); // compile ok, runtime errorarray2[0] = new C(); // compile ok, runtime ok
    } catch (Exception ex) {ex.printStackTrace();
    }
    
  • Java**(原始的)泛型是不变的**

    • String是Object的子类,list<String>和List<Object>没有关系
  • 泛型可采用通配符,支持协变和逆变

    • <? extends A>支持协变
    • <? super B>支持逆变
    • ArrayList<? extends A> list3 = new ArrayList<B>();   //协变
      ArrayList<? super B> list4 = new ArrayList<A>();      //逆变
      

方法情况:返回值是协变的

第三章 Java反射

1、反射入门

反射(Reflection):

  • 程序可以访问、检测和修改它本身状态或行为的能力,即自描述和自控制
  • 可以在运行时加载、探知和使用编译期间完全未知的类
  • 给Java插上动态语言特性的翅膀,弥补强类型语言的不足

反射作用

  • 在运行中分析类的能力
  • 在运行中查看和操作对象
    • 基于反射自由创建对象
    • 反射构建出无法直接访问的类
    • set或者get到无法访问到的成员变量
    • 调用不可访问的方法
  • 实现通用的数组操作代码
  • 类似函数指针的功能

创建对象的方法:

  1. 静态编码&编译
  2. 克隆(clone)
  3. 序列化(serialization)和反序列化(deserialization)
  4. 反射1
  5. 反射2

克隆实例:

public class B implements Cloneable {public void hello(){System.out.println("hello from B");}protected Object clone() throws CloneNotSupportedException{return super.clone();}
}
//obj3 是obj2的克隆对象  没有调用构造函数
B obj2 = new B();
obj2.hello();       B obj3 = (B) obj2.clone();
obj3.hello();

序列化和反序列化实例:

public class C implements Serializable {private static final long serialVersionUID = 1L;public void hello() {System.out.println("hello from C");}
}
//第三种 序列化  没有调用构造函数
//序列化会引发安全漏洞,未来将被移除出JDK,请谨慎使用!!!
C obj4  = new C();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.obj"));
out.writeObject(obj4);
out.close();   ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
C obj5 = (C) in.readObject();
in.close();
obj5.hello();

序列化和反序列化注意事项:

  • 可以通过序列化来存储对象的状态
  • 使用ObjectOutputStream来序列化对象。用FileOutputStream链接ObjectOutputStream来将对象序列化到文件上
  • 对象必须实现序列化接口才能被序列化。如果父类实现序列化,则子类自动地实现,而不管是否有明确的声明
  • 当对象被序列化时,整个对象版图都会被序列化。这代表它的实例变量所引用的对象也会被实例化。
  • 如果有不能被实例化的对象,执行期间就会抛出异常
  • 除非该实例变量被标记为transient,否则,该变量在还原的时候会被赋予null或基本数据类型的默认值
  • 在解序列化时,所有的类都必须能让JVM找到
  • 读取对象的顺序必须与写入的顺序相同
  • readObject()的返回类型是Object,因此解序列化回来的对象还需要转换成原来的类型
  • 静态变量不会被序列化,因为所有对象都是共享同一份静态变量

反射实例

//第四种  newInstance  调用构造函数
Object obj6 = Class.forName("A").newInstance();
Method m = Class.forName("A").getMethod("hello");
m.invoke(obj6);A obj7 = (A) Class.forName("A").newInstance();//第五种  newInstance  调用构造函数
Constructor<A> constructor = A.class.getConstructor();
A obj8 = constructor.newInstance();
obj8.hello();
2、反射关键类

反射:在运行中分析类的能力

Class:类型标识

三种获取方式:

String s1 = "abc";
Class c1 = s1.getClass();
System.out.println(c1.getName());Class c2 = Class.forName("java.lang.String");
System.out.println(c2.getName());Class c3 = String.class;
System.out.println(c3.getName());
  • 成员变量、方法、构造函数、修饰符、包、父类、父接口……

注:

  • getFields():返回本类和所有父类所有的public成员变量;getDeclaredFields():返回本类自己定义的成员变量,包括private的变量,但不包括父类的变量
  • getMethods():返回本类和所有父类所有的public方法;getDeclaredMethods():返回本类自己定义的方法,包括private的方法,但不包括父类的方法

通过反射调用方法

method.setAccessible(true);  //临时将private方法转为public,以方便使用方法
method.invoke(obj,null);    //invoke调用某个方法,参数:对象,实参。静态方法可以不用new对象

通过反射获取构造函数,进而创建类

3、反射应用

反射应用

  • 数据库连接

    • class.forName("com.mysql.cj.jdbc.Driver");
      
  • 数组扩容

    • java的数组一旦创建,其长度不再更改
    • 新建一个大数组(相同类型),然后将旧数组的内容拷贝过去
    public static void main(String[] args) {int[] a = { 1, 2, 3, 4, 5 };a = (int[]) goodCopy(a, 10);
    }public static Object goodCopy(Object oldArray, int newLength) {// Array类型Class c = oldArray.getClass();// 获取数组中的单个元素类型Class componentType = c.getComponentType();// 旧数组长度int oldLength = Array.getLength(oldArray);// 新数组Object newArray = Array.newInstance(componentType, newLength);// 拷贝旧数据System.arraycopy(oldArray, 0, newArray, 0, oldLength);return newArray;
    }
    
  • 动态执行方法

  • JSON和Java对象互转

  • Tomcat的Servlet对象创建

  • MyBatis的OR/M

  • Spring的Bean容器

  • org.reflections包介绍

    • Reflection的增强工具包
    • Java runtime metadata analysis
4、编译器API

反射

  • 可以查看对象的类型标识
  • 可以动态创建对象,访问器属性,调用其方法
  • 前提:类(class文件)必须先存在

编译器API

  • 对.java文件即时编译
  • 对字符串即时编译
  • 监听在编译过程中产生的警告和错误
  • 在代码中运行编译器(并非:runntime命令行调用javac命令)

JavaComplier

  • 自Java1.6推出,位于javax.tools包中
  • 可用在程序文件中的java编译器接口(代替javac.exe)
  • 在程序中编译java文件,产生class文件
  • run方法:较简单。可以编译java源文件,生成class文件,但不能指定输出路径,只能在源码所在目录下生成class文件。可以监控错误信息
  • getTask方法:更强大的功能。可以编译java源文件,包括在内存中的java文件(字符串),生成class文件。

run方法:

public static void successCompile() {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();// 第一个参数:输入流,null表示默认使用system.in// 第二个参数:输出流,null表示默认使用system.out// 第三个参数:错误流,null表示默认使用system.err// 第四个参数:String... 需要编译的文件名// 返回值:0表示成功,其他错误int result = compiler.run(null, null, null, "F:/temp/Hello1.java", "F:/temp/Hello2.java");System.out.println(0 == result ? "Success" : "Fail");
}public static void failCompile() throws UnsupportedEncodingException {ByteArrayOutputStream err = new ByteArrayOutputStream();JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, err, "F:/temp/Hello3.java");if (0 == result) {System.out.println("Success");} else {System.out.println("Fail");System.out.println(new String(err.toByteArray(), Charset.defaultCharset().toString()));}
}

getTask方法


Java编译器API作用:

  • JSP编译
  • 在线编程环境
  • 在线程序评判系统(Online Judge系统)
  • 自动化的构建和测试工具

第四章 Java代理

1、代理模式和静态代理

代理(Proxy):代替处理

代理模式(委托模式):为目标对象提供(包装)了一个代理,这个代理可以控制对目标对象的访问

  • 外界不用直接访问目标对象,而是访问代理对象,由代理对象再调用目标对象
  • 代理对象中可以添加监控和审查处理

Java代理:静态代理和动态代理

静态代理

  • 代理对象持有目标对象的句柄
  • 所有调用目标对象的方法,都调用代理对象的方法
  • 对每个方法,需要静态编码(理解简单,但代码繁杂)

eg:

Subject  //Interface,对象接口
public interface Subject{public void request();
}SubjectImpl    //目标对象
class SubjectImpl implements Subject{public void request(){System.out.println("I am dealing the request.");}
}StatucProxy    //静态代理对象,一般来说,静态代理对象和目标对象实现同一接口
class StaticProxy implements Subject{//实际目标对象private Subject subject;public StaticProxy(Subject subject){this.subject = subject;}public void request(){System.out.println("PreProcess");subject.request();System.out.println("PostProcess");}
}StaticProxyDemo    //静态代理模式
public class StaticProxyDemo {public static void main(String args[]){//创建实际对象SubjectImpl subject = new SubjectImpl();//把实际对象封装到代理对象中StaticProxy p = new StaticProxy(subject);p.request();}
}

静态代理模式

  • 隐藏实际的目标对象
  • 对方法的实现前后可以进行前置处理和后置处理
2、动态代理

动态代理

  • 对目标对象的方法每次被调用,进行动态拦截

代理处理器

  • 持有目标对象的句柄
  • 实现InvocationHandler接口
    • 实现invoke方法
    • 所有的代理对象方法调用,都会转发到invoke方法来
    • invoke的形参method,及时指代理对象方法的调用
    • 在invoke内部,可以根据method,使用目标对象不同的方法来相应请求

eg:

ProxyHandler //代理处理器
class ProxyHandler implements InvocationHandler{private Subject subject;public ProxyHandler(Subject subject){this.subject = subject;}//此函数在代理对象调用任何一个方法时都会被调用。@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(proxy.getClass().getName());//定义预处理的工作,当然你也可以根据 method 的不同进行不同的预处理工作System.out.println("====before====");Object result = method.invoke(subject, args);System.out.println("====after====");return result;}
}DynamicProxyDemo   //动态代理模式
public class DynamicProxyDemo {public static void main(String[] args) {//1.创建目标对象SubjectImpl realSubject = new SubjectImpl();    //2.创建调用处理器对象ProxyHandler handler = new ProxyHandler(realSubject); //3.动态生成代理对象Subject proxySubject = (Subject)Proxy.newProxyInstance(SubjectImpl.class.getClassLoader(),SubjectImpl.class.getInterfaces(), handler); //proxySubject真实类型com.sun.proxy.$Proxy0//proxySubject继承Proxy类,实现Subject接口//newProxyInstance的第二个参数,就是指定代理对象的接口//4.客户端通过代理对象调用方法//本次调用将自动被代理处理器的invoke方法接收proxySubject.request();    System.out.println(proxySubject.getClass().getName());System.out.println(proxySubject.getClass().getSuperclass().getName());}
}

代理对象

  • 根据给定的接口,由Proxy类自动生成的对象
  • 类型com.sun.proxy.$Proxy0,继承自java.lang.reflect.Proxy
  • 通常和目标对象实现同样的接口(可另实现其它的接口)
  • 实现多个接口
    • 接口的排序非常重要
    • 当多个接口里面有方法同名,则默认以第一个接口的方法调用

eg:

ProxyHandler //代理处理器
class ProxyHandler implements InvocationHandler{private Subject subject;public ProxyHandler(Subject subject){this.subject = subject;}//此函数在代理对象调用任何一个方法时都会被调用。@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(proxy.getClass().getName());//定义预处理的工作,当然你也可以根据 method 的不同进行不同的预处理工作System.out.println("====before====");Object result = method.invoke(subject, args);System.out.println("====after====");return result;}
}MultipleInterfacesProxyTest    //多接口代理模式,以第一个接口为主
public class MultipleInterfacesProxyTest {public static void main(String[] args) throws Exception {Cook cook = new CookImpl();ClassLoader cl = MultipleInterfacesProxyTest.class.getClassLoader();ProxyHandler handler = new ProxyHandler(cook);//生成代理类型Class<?> proxyClass = Proxy.getProxyClass(cl, new Class<?>[]{Cook.class,Driver.class});//这一步,如果交换接口顺序,编译就会报错//生成代理对象Object proxy = proxyClass.getConstructor(new Class[]{InvocationHandler.class}).newInstance(new Object[]{handler});System.out.println(Proxy.isProxyClass(proxyClass));Proxy p = (Proxy) proxy;System.out.println(p.getInvocationHandler(proxy).getClass().getName());        System.out.println("proxy类型:" + proxyClass.getName());//代理对象都继承于java.lang.reflect.Proxy,但是获取父类确是Object而不是ProxyClass father = proxyClass.getSuperclass();System.out.println("proxy的父类类型:" + father.getName());Class[] cs = proxy.getClass().getInterfaces();for(Class c:cs){System.out.println("proxy的父接口类型:" + c.getName());}System.out.println("=====================");Method[] ms = proxy.getClass().getMethods();for(Method m:ms){System.out.println("调用方法 " + m.getName() + ";参数为 " + Arrays.deepToString(m.getParameters()));}System.out.println("=====================");Cook c = (Cook) proxy;c.doWork();Driver d = (Driver) proxy;d.doWork();     }
}
3、AOP编程

AOP:Aspect Oriented Programming

面向切面编程(VS 面向对象编程)

  • 面向切面:将通用需求功能从众多类中分离出来,使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需修改这个行为即可
  • 不是取代OOP编程,而是OOP的补充

面向切面编程优点:

  • 分离代码的耦合(高内聚,低耦合)
  • 业务逻辑变化不需要修改源代码/不用重启
  • 加快编程和测试速度

第五章 Java注解

1、注解入门

注解(Annotation)

  • 位于源码中(代码/注释/注解),使用其他工具进行处理的标签
  • 注解用来修饰程序的元素,但不会对被修饰的对象有直接的影响
  • 只有通过某种配套的工具才会对注解信息进行访问和处理
  • 主要用途
    • 提供信息给编译器/IDE工具
    • 可用于其他工具来产生额外的代码/配置文件等
    • 有一些注解可在程序运行时访问,增加程序的动态性
2、Java预定义的普通注解

(部分注解)

注解 含义 用途
@Override 继承和覆写 修饰方法,检查该方法是父类的方法;强制该函数代码必须符合父类中该方法的定义;避免代码错误
@Deprecated 废弃
@SuppressWarnings 压制警告 压制各种不同类型的警告信息,使得编译器不显示警告;警告类型名称是编译器/IDE工具自己定义的,Java规范没有强制要求
@SafeVarargs 不会对不定项参数做危险操作
@FunctionInterface 声明功能性接口

@SuppressWarnings

  • SuppressWarnings(“unchecked”) 忽略unchecked警告信息
  • SuppressWarnings(“deprecated”) 忽略deprecated警告信息
  • SuppressWarnings({“unchecked”,“deprecated”}) 忽略两种警告信息
  • SuppressWarnings(value={“unchecked”,“deprecated”}) 同上
  • SuppressWarnings(“all”) 忽略所有警告信息
  • 其他警告类型
    • cast,忽略类转型警告
    • serial,忽略实现Serializable接口的,没有定义serialVersionUID
3、自定义注解

eg:

//带着一个成员属性的一个注解
public @interface SingleTest{int value() default 0;//String para();
}

注解可以包括的类型:

  • 8种基本数据类型
  • String
  • Class
  • enum类型
  • 注解类型
  • 由前面类型组成的数组

eg:

public @interface BugReport {enum Status {UNCONFIRMED, CONFIRMED, FIXED, NOTABUG};boolean showStopper() default true;String assiganedTo() default "[none]";Status status() default Status.UNCONFIRMED;String[] reportedBy();
}

注解使用:使用时可以给注解成员赋值

注解使用的位置:

  • @Target可以限定位置
4、Java预定义的元注解

元注解:用来修饰注解的注解

元注解 注解说明
@Target 设置目标范围
@Retention 设置保持性
@Inherited 注解继承
@Repeatable 此注解可以重复修饰
@Document 文档

Retention(保留)

  • 实例:@Retention(RetentionPolicy.RUNTIME)
  • 这个注解用来修饰注解的存在范围
    • RetentionPolicy.SOURCE注解仅存在源码,不在class文件。eg:override
    • RetentionPolicy.CLASS这是默认的注解保留策略。注解存在于.class文件,但是不能被JVM加载。
    • RetentionPolicy.RUNTIME这种策略下,注解可以被JVM运行时访问到。通常情况下,可以结合反射来做一些事情。

Target

  • 限定目标注解作用位置

    • ElementType.ANNOTATION_TYPE(注:修饰注解)
    • CONSTRUCTOR/FIELD/LOCAL_VARIABLE/METHOD/PACKAGE/PARAMETER/TYPE

Inherited

  • 让一个类和它的子类都包含某个注解
  • 普通的注解没有继承功能

Repeatable

  • 表示被修饰的注解可以重复应用标注
  • 需要定义注解和容器注解
RepeatableAnnotation //重复元注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableAnnotations.class)
public @interface RepeatableAnnotation {int a() default 0;int b() default 0;int c() default 0;
}RepeatableAnnotations  //重复元注解容器
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnotations {RepeatableAnnotation[] value();
}Student    //方法类
public class Student {  @RepeatableAnnotation(a=1,b=2,c=3)@RepeatableAnnotation(a=1,b=2,c=4)public static void add(int a, int b, int c){if(c != a+b){throw new ArithmeticException("Wrong");}}
}Main
public static void main(String[] a) throws Exception
{String className = "repeatable.Student";for (Method m : Class.forName(className).getMethods()) {if (m.isAnnotationPresent(RepeatableAnnotations.class)) {RepeatableAnnotation[] annos = m.getAnnotationsByType(RepeatableAnnotation.class);for (RepeatableAnnotation anno : annos) {System.out.println(anno.a() + "," + anno.b() + "," + anno.c());try {m.invoke(null,anno.a(),anno.b(),anno.c());} catch (Throwable ex) {System.out.printf("Test %s failed: %s %n", m, ex.getCause());}}            }}
}

Documented

  • 指明这个注解可以被Javadoc工具解析,形成帮助文档
5、注解的解析

RetentionPolicy.RUNTIME:注解在class文件中,被JVM加载,可用反射解析注解

  • Class.getAnnotations() 获取注解数组
  • Class.isAnnotation() 是否有注解
  • Class.isAnnotationPresent(Class annotationClass) 是否是某个注解
  • Method/Field/Constructor.getAnnotations()/isAnnotationPresent(Class annotationClass)

RetentionPolicy.CLASS:注解在class文件中,但没有被JVM加载

  • 只能使用字节码工具进行特殊处理

RetentionPolicy.SOURCEC:注解在java文件中,不在class文件中,也不会被JVM加载

  • 只有在源码级别进行注解处理
  • Java提供注解处理器来解析带注解的源码,产生新的文件
6、RUNTIME注解的实现本质

注解:Annotation

  • 位于源码中(代码+注释+注解),使用其他工具进行处理的标签
  • 根据RetentionPolicy的不同,分成三种类型的注解:
    • SOURCE:注解在源码,不在class文件
    • CLASS:注解在源码和class文件,但JVM不加载
    • RUNTIME:注解在源码和class文件中,JVM加载
7、注解的应用

第六章 嵌套类

1、嵌套类入门

嵌套类(Nested classes):简单地说,就是在一个类的内部创建另一个类。

  • 静态嵌套类:类前面有static修饰符
  • 非静态嵌套类:又名内部类,Inner classes
    • 普通内部类(成员内部类) (放在类的内部)
    • 局部内部类(Local classes) (放在方法的内部)
    • 匿名内部类(Anonymous classes)

为什么需要嵌套类:

  • 不同的访问权限要求,更细粒度的访问控制
  • 简洁,避免过多的类定义
  • 更好的封装

嵌套类学习重点:

  • 嵌套类的语法
  • 嵌套类和其他类的关系
    • 嵌套类访问外部包围类
    • 外部包围类访问嵌套类
    • 第三方类访问嵌套类
2、匿名内部类和局部内部类

匿名内部类:Anonymous classes

  • 没有类名的内部类,必须继承一个父类/实现一个父接口
  • 在实例化以后,迅速转型为父类/父接口
  • 这种类型的对象,只能new一个对象,之后以对象名字操作
  • 注意事项:
    • 匿名内部类中不能定义静态变量和静态方法,除非是常量
    • 没有类名,没有构造函数,能用父类/父接口的构造函数
    • 没有类名,外部包围类和其他类也无法访问到匿名内部类

局部内部类:Local classes (放在方法的内部)

  • 只能活在这个代码块中,代码块结束后,外界无法使用该类
  • 外部包围类.this.变量名,可以访问到外部包围类的成员变量
  • 注意事项:
    • 同匿名内部类,不能定义静态变量和静态方法,除非是常量

匿名内部类和局部内部类总结:

  • 两者几乎相似
  • 局部内部类可以重用,匿名内部类不可以重用
  • 匿名内部类更简洁
3、普通内部类和静态嵌套类

普通内部类

  • 注意事项

    • 同匿名内部类,不能定义静态变量和静态方法,除非是常量

    • 可以用private/default(不写)/protected/public控制外界访问

    • 和外部包围类的实例相关,一个普通内部类实例肯定是在一个外部包围类的实例中

    • 在第三方类中,需要先创建外部包围类实例,才能创建普通内部类的实例,不允许单独的普通内部类对象存在

      创建方式:外部包围类.new.普通内部类();

静态嵌套类

  • 与其他三类不同:静态嵌套类可以定义静态成员

  • 只能访问到外部嵌套类的静态成员

    • 可通过包围类的对象进行访问非静态成员
  • 第三方需要通过外部包围类才可以访问到静态嵌套类,并创建其对象。但是不需要外部包围类的实例

    创建方式:new 外部包围类.静态嵌套类();

  • 静态嵌套类和一个顶层类并没有什么区别,纯粹是为了packaging的方便

4、嵌套类对比

5、嵌套类应用

匿名内部类:

  • 无需类名,用过即焚,使用广泛
  • 该类的对象只要一个,且方法只有一个,代码短
    • Android中常用匿名内部类

局部内部类:

  • 介于匿名内部类和普通内部类之间,使用较少

    • 只用一次,就用匿名内部类,简便
    • 使用多次,就上升到普通内部类,整个类都可以使用

普通内部类:

  • 广泛应用在具有母子结构的类,内部类对象和外部类保持联系

    • 如Map和Map.Entry等

静态嵌套类:

  • 和普通类一致,只是“碰巧”声明在一个外围类的内部
  • 如果不需要访问外围类的非静态成员,尽量将普通内部类变更为静态嵌套类
    • 节省普通内部类和外围类的联系开销
    • 使得外围类对象更容易被垃圾回收器回收

第七章 Lambda表达式

1、Lambda表达式入门

Lambda表达式:传递方法/代码块(函数式程序语言设计)

Lambda表达式形式:

(input parameters) -> expression
  • 参数,箭头,一个表达式
(String first,String second) -> first.length()-second.length()
  • 参数,箭头,{多个语句}
(first,second) ->{//形参可以不写类型,可以从上下文推断出来int ressult = (-1) * (first.length() - second.length());return result;
}

Lambda表达式

类似于匿名方法,一个没有名字的方法

参数,箭头,表达式语句

可以忽略写参数类型

坚决不声明返回值类型。如果有返回值类型,返回值类型会在上下文推断出来的,无需声明

没有权限修饰符

单句表达式,将直接返回值,不用大括号

特例

带return语句,算多句,必须使用大括号

无参数,仅保留括号,箭头,表达式

一个参数,可省略括号

2、函数式接口

函数式接口(Functional Interface)

  • 是一个接口,符合Java接口的定义
  • 只包含一个抽象方法的接口
  • 可以包括其他的default方法、static方法、private方法
  • 由于只有一个未实现的方法,所以Lambda表达式可以自动填上这个尚未实现的方法
  • 采用Lambda表达式,可以自动创建出一个(伪)嵌套类的对象(没有实际的嵌套类class文件产生),然后使用,比真正嵌套类更加轻量,更加简洁高效

个人理解

Lambda表达式只能应用于函数式接口中,函数式接口中,只有一个未实现的方法,即有定义而无实现;Lambda表达式有实现而无定义。故而,Lambda表达式自动替换函数式接口中的抽象方法的实现。

Comparator接口:有2个未实现的方法

  • compare、equals
  • 任何实现Comparator接口的类,肯定继承了Object,也就有equals实现

自定义函数式接口

@FunctionalInterface
//系统自带的函数式接口注解,用于编译器检查
public interface StringChecker {public boolean test(String s);
}public static void main(String[] args) {String[] arr = new String[]{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};StringChecker evenLength = s -> {if (s.length() % 2 == 0)return true;return false;};for (String s : arr) {if (evenLength.test(s)) {System.out.println("s = " + s);}}
}

系统自带的函数式接口

  • 涵盖大部分常用的功能,可以重复使用
  • 位于java.util.function包中
  • 常用的函数式接口

函数式接口 方法
predicate test
consumer accept
supplier get
function apply

eg:

predicate

String[] arr = new String[]{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};Predicate<String> oddLength = s -> s.length() % 2 == 0 ? false : true;
for (String s : arr) {if (oddLength.test(s)){System.out.println("s = " + s);}
}

consumer

Consumer<String> printer=s -> System.out.println("printer = " + s);
for (String p : arr) {printer.accept(p);
}

function

Supplier<String> supplier = () -> arr[m];
System.out.println("supplier = " + supplier.get());
3、方法引用

方法引用:Method Reference

  • Lambda表达式支持传递现有的类库函数

eg:

String[] arr = new String[]{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
Arrays.sort(arr, String::compareToIgnoreCase);
System.out.println("Arrays.toString(arr) = " + Arrays.toString(arr));

方法引用:

  • Class::staticMethod,如Math::abs方法,等价于x->Math.abs(x)
  • Class::instanceMethod,如String::compareToIgnoreCase方法,等价于(x,y)->x.compareToIgnoreCase(y)
  • object::instanceMethod,如System.out::println方法,等价于x->System.out.println(x)
    • 支持this::instanceMethod调用
    • 支持super::instanceMethod调用
  • Class::new,调用某类构造函数,支持单个对象构建
  • Class[]::new,调用某类构造函数,支持数组对象构建
4、Lambda表达式应用
  • 类似于匿名方法,一个没有名字的方法
  • 被赋值后,可以看做是一个函数式接口的实例(对象)
  • 但是Lambda表达式没有存储目标类型的信息

注意:

  • Lambda表达式没有变量遮蔽问题,因为它的内容和嵌套块有着相同的作用域

    • 所以,在Lambda表达式中,不可以声明与(外部嵌套块)局部变量同名的参数或者局部变量
  • Lambda的this指代。表达式中的this,是指创建这个表达式的方法的this参数。

    • 因为Lambda表达式是先在方法中执行完毕后,再将函数式接口中的方法替换
    • 所以Lambda表达式缺点:无法获得自身的引用(this)
  • 优先级:即使用时优先考虑

    • 方法引用>Lambda表达式>嵌套类
    • Lambda表达式:系统自带函数式接口>自定义函数式接口

第八章 Java Stream流

1、流的概述

Stream流:a sequence elements from source that supports aggregate operations

Stream流的两个特性:

  1. pipelining:很多流操作也是返回一个流(流对象的流操作返回流对象)
  2. Internal Iteration:流操作进行迭代,用户感知不到循环遍历(流自动进行内部迭代)

Stream语法

  • 类似SQL语句,遵循“做什么而非怎么做”原则

流的工作流程

  1. 流的创建
  2. 流的转换,将流转换为其他流的中间操作,可包括多个步骤(惰性操作)
  3. 流的计算结果。这个操作会强制执行之前的惰性操作。这个步骤以后,流就不会再用了。
    • 注意:只有经过第三步流计算,才能执行第二步的流转换。也就是说,第二步只是将表达式写出,而并没有启动运行
2、流的创建

流的创建

1、Collection接口的stream方法

Stream<String> stream = new ArrayList<String>().stream();

2、Arrays.stream可以将数组转为Stream

Stream<String> b1 = Arrays.stream("a,b,c,d,e".split(","), 3, 5);

3、利用Stream类进行转化

  • of方法,直接将数组转化
Stream<Integer> c1 = Stream.of(new Integer[5]);
  • empty方法,产生一个空流
Stream<String> d1 = Stream.empty();
  • generate方法,接收一个Lambda表达式
Stream<String> generate = Stream.generate(() -> "hello");
Stream<Double> generate1 = Stream.generate(Math::random);
  • iterate方法,接收一个种子,和一个Lambda表达式
Stream.iterate(1.0, n -> n * 2);

4、其他类/方法产生Stream流

  • Files.lines方法
Stream<String> contents = Files.lines(Paths.get(fileName));
  • Pattern的splitAsStream方法
Stream<String> words = Pattern.compile(",").splitAsStream("a,b,c");

基本类型流(只有三种)

  • IntStream,LongStream,DoubleStream
IntStream s1 = IntStream.of(1, 2, 3, 4, 5);
s1 = Arrays.stream(new int[]{1, 2, 3});
s1 = IntStream.generate(() -> (int) (Math.random() * 100));
s1 = IntStream.range(1, 5);    //1,2,3,4   step 1
s1 = IntStream.rangeClosed(1, 5);  //1,2,3,4,5IntStream s2 = IntStream.of(1, 2, 3, 4, 5);
Stream<Integer> s3 = s2.boxed();    //基本类型流-->对象流
IntStream s4 = s3.mapToInt(Integer::intValue);  //对象流-->基本类型流

并行流

  • 使得所有的中间转换操作都将被并行化
  • Collections.parallelStream()将任何集合转为并行流
  • Stream.parallel()方法,产生一个并行流
  • 注意:需要保证传给并行流的操作不存在竞争
IntStream stream1 = IntStream.range(1, 100000000);
long evenNum = stream1.parallel().filter(n -> n % 2 == 0).count();
System.out.println("evenNum = " + evenNum);
3、流的转换

流的转换,是从一种流到另外一种流

  • 过滤,去重
  • 排序sorted
  • 转化
  • 抽取/跳过/连接
  • 其他

过滤filter

  • filter(Predicate<? super T> predicate)
    
  • 接收一个Lambda表达式,对每个元素进行判定,符合条件的留下

去重distinct

  • 对流的元素进行过滤,去除重复,只留下不重复的元素

转化map

  • 利用方法引用/Lambda表达式对流每个元素进行函数计算,返回stream

转化

  • 抽取limit:限制返回流的元素个数
  • 跳过skip:跳过前面的n个元素
  • 连接concat:将流的结果连接起来

其他

  • 额外调试peek:
Stream<Double> limit = Stream.iterate(1.0, n -> n * 2).peek(n -> System.out.println("number:" + n)).limit(5);
4、Optional类型

Optional<T> 解决NullPointerException异常问题

  • 一个包装器对象

    • 数据对象容器
    • Java用来解决NullPointerException的一把钥匙
    • 在流运算中,避免对象是否null的判定
  • 要么包装了类型T的对象,要么没有包装任何对象(还是null)
  • 如果T有值,那么直接返回T的对象
  • 如果T是null,那么可以返回一个替代物
Optional<String> s1 = Optional.of(new String("abc"));
String s2 = s1.get();
System.out.println("s2: " + s2); //abcOptional<String> s3 = Optional.empty();
String s4 = s3.orElse("def");
System.out.println("s4: " + s4); //def

Optional<T>创建

  • of方法
  • empty方法
  • ofNullable方法,对于对象有可能为null情况下,安全创建
Optional<String> s5 = Optional.of(new String("abc"));
Optional<String> s6 = Optional.empty();
String s7 = null;
Optional<String> s8 = Optional.ofNullable(s7);
//s7不为Null,s8就是s7, 否则s8就为Optional.empty()

Optional<T>使用

  • get,获取值,不安全的用法。NoSuchElementException异常
  • orElse方法,获取值,如果为null,采用替代物的值
  • orElseGet方法,获取值,如果为null,采用Lambda表达式值返回
  • orElseThrow方法,获取值,如果为null,抛出异常
  • ifPresent方法,判断是否为空,不为空返回true
  • ifPresent(Consumer),判断是否为空,如果不为空,则进行后续Consumer操作,如果为空,则不做任何事情
  • map(Function),将值传递给Function函数进行计算。如果为空,则不计算
5、流的计算结果

流的计算

  1. 约简

    • 简单约简(聚合函数):count/max/min/……

      • count
      • max/min(Comparator),最大/小值,需要比较器
      • findFirst(),找到第一个元素;findAny(),找到任意一个元素
      • anyMatch(Predicate),如有任意一个元素满足Predicate,返回true;AllMatch,所有元素满足Predicate,返回true;noneMatch(Predicate),如没有任何元素满足Predicate,返回true
    • 自定义约简:reduce

      • reduce,传递一个二元函数BinaryOperator,对流元素进行计算。如求和,求积、字符串拼接等
      Optional<Integer> sum = s1.reduce(Integer::sum);
      Optional<Integer> product = s2.reduce((x,y)->x*y);
      
  2. 查看/遍历元素:iterator/forEach

  3. 存放到数据结构中

    • toArray(),将结果转为数组
    • collect(Collectors.toList()),将结果转为List
    • collect(Collectors.toSet()),将结果转为Set
    • collect(Collectors.toMap()),将结果转为Map
    • collect(Collectors.joining()),将结果连接起来
6、流的应用

stream应用注意事项:

  1. 一个流,一次只能一个用途,不能多个用途,用了之后不能再用

  2. 避免创建无限流

    //无限流
    IntStream.iterate(0, i -> i + 1).forEach(System.out::println);//需要对流元素进行限制
    IntStream.iterate(0, i -> i + 1)
    .limit(10).forEach(System.out::println);//又一个无限流
    IntStream.iterate(0, i -> ( i + 1 ) % 2)
    .distinct().limit(10).forEach(System.out::println);
    
  3. 注意操作顺序

  4. 谨慎使用并行流

    • 底层使用Fork-Join Pool,处理计算密集型任务
    • 数据量过小不用
    • 数据结构不容易分解的使用不用,如LinkedList等
    • 数据频繁拆箱装箱不用
    • 设计findFirst或者limit的时候不用
  5. Stream VS Collection

    • stream和collection两者可以相互转化
    • 如果数据可能无限,用stream
    • 如果数据很大很大,用stream
    • 如果调用者将使用查找/过滤/聚合等操作,用stream
    • 当调用者使用过程中,发生数据改变,而调用者需要对数据一致性有较高要求,用Collection

第九章 Java模块化

1、Java模块化概述

模块化必须遵循的三个原则:

  1. 强封装性:一个模块必须能够对其他模块隐藏其部分代码
  2. 定义良好的接口:模块必须向其他模块公开良好且稳定的接口
  3. 显式依赖:明确一个模块需要哪些模块的支持才能完成工作

java9开始引入新的模块化系统:Jigsaw拼图

  • 以模块(module)为中心
  • 对JDK本身进行模块化
  • 提供一个应用程序可以使用的模块系统
  • 优点
    • 可靠的配置
    • 强封装性
    • 可扩展开发
    • 安全性
    • 性能优化
2、模块创建和运行

Java Jigsaw

  • 自Java 9推出,以模块为中心

  • 在模块中,仍以包-类文件结构存在

  • 每个模块中,都有一个module-info.java

    • 说明这个模块依赖哪些其它的模块,输出本模块中哪些内容
    module java.prefs{requires java.xml; //本模块需依赖的模块exports java.util.prefs; //输出给别人使用
    }
    
3、模块信息文件
4、服务
5、Java模块化应用

第十章 Java字节码

1、Java字节码概述
2、Java字节码文件构成

jdk提供了javap来对class文件做反汇编

  1. 前4个字节为魔数,十六进制表示为0xCAFEBABE,标识该文件为class文件
  2. 第5、6字节表示次版本号,第7、8字节表示主版本号
    • 主版本号与jdk版本有映射关系
  3. 常量池 主要存放两大类常量
    • 字面量,如文本字符串、final的常量值等
    • 符号引用
      • 类和接口的全限定名
      • 字段的名称和修饰符
      • 方法的名称和描述符
  4. 访问标志 (类的修饰符,即该Class是类还是接口,以及其修饰符)
  5. 类索引、父类索引、接口索引集合
    • 描述的是当前类/父类/接口集合的全限定名,保存的是常量池中的索引值
  6. 字段表 成员变量
  7. 方法表 成员方法
  8. 附加属性。该项存放了在该文件中类或接口所定义属性的基本信息
    • 属性信息相对灵活,编译器可自由写入属性信息,JVM会忽略不认识的属性信息
3、Java字节码指令分类
  1. 加载和存储指令

    • 用于将数据在栈帧中的局部变量表和操作数栈之间来回传输
    • 将一个局部变量加载到操作栈:iload,lload.fload,dload,aload等
    • 将一个数值从操作栈存储到局部变量表:istore,Istore,fstore,dstore,astore等
    • 将一个常量加载到操作栈:bipush,sipush,Idc,Idc_w,Idc2_w,aconst_null,iconst_m1等
  2. 运算指令
    • iadd,isub,imul,idiv等
  3. 类型转换指令
  4. 对象/数组创建与访问指令
  5. 操作栈管理指令
    • pop,dup等
  6. 控制转移指令
  7. ……
  • JVM指令由操作码和零至多个操作数组成

    • 操作码
    • 操作数(操作所需参数)
  • JVM的指令集是基于栈而不是寄存器

    • 字节码指令控制的是JVM操作栈
4、Java字节码操作

ASM:是生成、转换、分析class文件的工具包(第三方库)

5、Java字节码增强
  • 字节码操作:通常在字节码使用之前完成

    • 源码、编译、(字节码操作)、运行
  • 字节码增强:运行时对字节码进行修改/调换
6、Java字节码混淆

字节码保护

  • 字节码加密

    • 对字节码进行加密,不再遵循JVM制定的规范
    • JVM加载之前,对字节码解密后,再加载
  • 字节码混淆
    • 被混淆的代码依然遵循JVM制定的规范
    • 变量命名和程序流程上进行等效替换,使得程序的可读性变差
    • 代码难以被理解和重用,达到保护代码的效果

ProGuard

  • 最著名的Java字节码混淆器
  • 除了混淆,还具有代码压缩、优化、预检等功能
  • 可以命令行运行,也可以集成到IDE
  • 也可以与Maven相结合
  • 注意事项
    • 反射调用类或者方法,可能失败
    • 对外接口的类和方法,不要混淆,否则调用失败
    • 嵌套类混淆,导致调用失败
    • native的方法不要混淆
    • 枚举类不要混淆
    • ……
7、Java字节码总结和展望

第十一章 Java类加载器

1、Java类加载机制

类加载器ClassLoader

  • 负责查找、加载、校验字节码的应用程序
  • java.lang.ClassLoader
    • load(String className)根据名字加载一个类,返回类的实例
    • defineClass(String name,byte[] b,int off,int len)将一个字节流定义一个类
    • findClass(String name)查找一个类
    • findLoadedClass(String name)在已加载的类中,查找一个类
    • 成员变量ClassLoader parent;
  • JVM四级类加载器
    • 启动类加载器(Bootstrap),系统类rt.jar
    • 扩展类加载器(Extension),jre/lib/ext
    • 应用类加载器(App),classpath
    • 用户自定义加载器(Plugin),程序自定义

  • 类加载器双亲委托模型

    • 首先判断是否已经加载
    • 若无,找父加载器(父父类加载器)加载
    • 若再无,由当前加载器加载

eg:

public class ClassLoaderTree {public static void main(String[] args) {ClassLoader c = ClassLoaderTree.class.getClassLoader();while(null != c){System.out.println(c.getClass().getName());c = c.getParent();}if(null == c){System.out.println("BootstrapLoader is implemented by C ");}}
}结果:
jdk.internal.loader.ClassLoaders$AppClassLoader
jdk.internal.loader.ClassLoaders$PlatformClassLoader
BootstrapLoader is implemented by C
2、Java类双亲委托加载扩展

Java严格执行双亲委托机制

  • 类会由最顶层的加载器来加载,如没有,才由下级加载器加载
  • 委托是单向的,确保上层核心的类的正确性
  • 但是上级类加载器所加载的类,无法访问下级类加载器所加载的类
    • 例如,java.lang.String无法访问自定义的一个Test类
    • Java是一个遵循契约设计的程序语言,核心类库提供接口,应用层提供实现
    • 核心类库是BootstrapClassLoader加载
    • 应用层是AppClassLoader加载
    • 典型例子是JDBC和XML Parser等

解决问题:如何使得上层类加载器的类能够使用下级类加载器的类

  • 双亲委托的补充

    1. 执行java,添加虚拟机参数-Xbootclasspath/a:path,将类路径配置为Bootstrap等级
    2. 使用ServiceLoad.load方法,来加载(底层加载器所加载的类),如JDBC
      • java.sql.DriverManager是Bootstrap加载器加载的,需要访问到com.mysql.jdbc.Driver类
3、自定义类加载路径

用户自定义类加载器

  • 自定义加载路径

    • 弥补类搜索路径静态的不足
    • URLClassLoader,从多个URL(jar或者目录)中加载类
  • 自定义类加载器

    • 继承ClassLoader类
    • 重写findClass(String className)方法

自定义加载路径

  • 继承于ClassLoader
  • 程序运行时增加新的类加载路径
  • 可从多个来源中加载类
    • 目录
    • jar包
    • 网络
  • addURL添加路径
  • close方法关闭

eg:

//URL支持http, https, file, jar 四种协议
URL url = new URL("file:D:\\software\\IDM\\download\\Java核心技术_高阶\\PMOOC11-03-First\\bin\\");
//URL url = new URL("file:C:/Users/Tom/Desktop/PMOOC11-03-First.jar");//程序运行时,添加一个classpath路径
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class<?> c = loader.loadClass("edu.ecnu.Hello");//采用反射调用
Method m = c.getMethod("say");
m.invoke(c.newInstance());
System.out.println(c.getClassLoader());
System.out.println(c.getClassLoader().getParent());
4、自定义类加载器

自定义类加载器

  • 继承ClassLoader类
  • 重写findClass(String className)方法
  • 使用时,默认先调用loadClass(className)来查看是否已经加载过,然后委托双亲加载,如果都没有,再通过findClass加载返回
    • 在findClass中,首先读取字节码文件
    • 然后,调用defineClass(className,bytes,off,len)将类注册到虚拟机中
    • 可以重写loadClass方法来突破双亲加载(不推荐使用)

eg:

自定义类加载器CryptoClassLoader

public Class<?> findClass(String name) throws ClassNotFoundException
{try{byte[] classBytes = null;//读取Hello.class文件,得到所有字节流classBytes = loadClassBytes(name);      //自定义的一个方法,获取hello.class文件字节流//调用defineClass方法产生一个类,并在VM中注册Class<?> cl = defineClass(name, classBytes, 0, classBytes.length);if (cl == null) throw new ClassNotFoundException(name);return cl;}catch (IOException e){throw new ClassNotFoundException(name);}
}

自定义类加载器调用

ClassLoader loader = new CryptoClassLoader();//loadClass去加载Hello类//loadClass是ClassLoader默认方法,通过委托双亲去加载类//如加载不到,则调用findClass方法加载Class<?> c = loader.loadClass("edu.ecnu.Hello");Method m = c.getMethod("say");m.invoke(c.newInstance());System.out.println(c.getClassLoader().getClass().getName());

注意:同一个类可以被不同层级的加载器加载,且作为2个类对待

5、Java类加载器总结与展望

JVM四级类加载器的动态性

  1. 使用虚拟机参数-Xbootclasspath,将jar或目录提高到Bootstrap等级
  2. 使用ServiceLoader和SPI机制,实现上层加载器的类,访问下层加载器的类
  3. 使用URLClassLoader,可以在运行时增加新的classpath路径
  4. 使用自定义的ClassLoader,可以通过重写findClass方法动态加载字节码,还可以在加载字节码过程中进行修改/校验等操作

第十二章 JVM内存管理

1、JVM概述

2、JVM内存分类

Java自动内存管理

  • 传统程序语言:由程序员手动内存管理

    • C/C++,malloc申请内存和free释放内存
    • 由于程序员疏忽或程序异常,导致内存泄漏
  • 现代程序语言:自动内存管理
    • Java/C#,采用自动内存管理
    • 程序员只需要申请使用,系统会检查无用的对象并回收内存
    • 系统统一管理内存,内存使用相对高效,但也会出现异常

JVM内存分类

  • 线程私有内存

    • 寄存器(程序计数器)(Program Counter Register)
    • Java虚拟机栈(JVM Stack)
    • 本地方法栈(Native Method Stack)
  • 多线程共享内存
    • 堆(Heap)
    • 方法区(Method Area)
      • 运行时常量池

程序计数器(Program Counter Register)

  • 一块小内存,每个线程都有
  • PC存储当前方法
    • 线程正在执行的方法称为该线程的当前方法
    • 当前方法为本地(native)方法时,pc值未定义(undefined)
    • 当前方法为非本地方法

JVM栈

  • 每个线程有自己独立的Java虚拟机栈,线程私有

    • Xss设置每个线程栈大小
  • Java方法的执行基于栈
  • 引发的异常
    • 栈的深度超过虚拟机规定深度,StackOverflowError异常
    • 无法扩展内存,OutOfMemoryError异常

本地方法栈

  • 存储native方法的执行信息,线程私有

  • 虚拟机启动时创建,所有线程共享,占地最大
  • 对象在堆上分配内存
  • 垃圾回收的主要区域
  • 设置大小
    • Xms初始堆值,Xmx最大堆值
  • 引发的异常 无法满足内存分配要求,OutOfMemoryError异常

方法区

  • 存储JVM已经加载类的结构,所有线程共享

    • 运行时常量池、类信息、常量、静态变量等
  • JVM启动时创建,逻辑上属于堆(Heap)的一部分
  • 很少做垃圾回收
  • 运行时常量池(Run-Time Constant Pool)
    • Class文件中常量池的运行时表示
    • 属于方法区的一部分
    • 动态性:Java语言并不要求常量一定只有在编译期产生

3、JVM内存参数

  • 共享,内存大户,存储所有的对象和数组
  • Xms初始堆值,-Xmx最大堆值

  • 线程私有,存储类中每个方法的内容
  • Xss最大栈值
4、Java对象引用

JVM内置有垃圾收集器

  • GC,Garbage Collector
  • 自动清除无用的对象,回收内存

垃圾收集器的工作职责

  • 什么内存需要收集(判定无用的对象)
  • 什么时候回收(何时启动,不影响程序正常运行)
  • 如何回收(回收过程,要求速度快/时间短/影响小)

Java对象的生命周期

  • 对象通过构造函数创建,但是没有析构函数回收
  • 对象存活在离它最近的一对大括号中

垃圾收集器基于对象引用判定无用对象

  • 零引用,互引用等

Java四种对象引用:

WeakReference:只能存活到下一次垃圾回收前

5、垃圾收集算法

垃圾收集器如何回收(回收过程,要求速度快/时间短/影响小)

1、引用计数法

  • 每个对象都有一个引用计数器。有引用,计数器+1,当引用失效,计数器-1。计数器为0,的对象,将被回收
  • 优缺点
    • 优点:简单,效率高
    • 缺点:无法识别对象之间相互循环引用

2、标记-清除

  • 标记阶段:标记出所有需要回收的对象
  • 回收阶段:统一回收所有被标记的对象
  • 优缺点
    • 优点:简单
    • 缺点:效率不高;内存碎片

3、复制算法

  • 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块
  • 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面
  • 然后再把已使用过的内存空间一次清理掉
  • 优缺点
    • 优点:简单、高效
    • 缺点:可用内存减少;对象存活率高时复制操作较多

4、标记-整理

  • 标记阶段:与“标记-清理”算法一样
  • 整理阶段:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
  • 优缺点
    • 优点:避免碎片产生;无需两块相同内存
    • 缺点:计算代价大,标记清理+碎片整理;更新引用地址

5、分代收集(现在标准JVM普遍采用的垃圾收集算法)

  • Java对象的生命周期不同,有长有短
  • 根据对象存活周期,将内存划分新生代和老年代
  • 新生代
    • 主要存放短暂生命周期的对象
    • 新创建的对象都先放在新生代,大部分新建对象在第一次gc时被回收
  • 老年代
    • 一个对象经过几次gc仍存活,则放入老年代
    • 这些对象可以存活很长时间,或者伴随程序一生,需要常驻内存的,可以减少回收次数
  • 针对各个年代特点采用合适的收集算法
    • 新生代 复制算法
    • 老年代 标记清除或标记整理

6、JVM堆内存参数和GC跟踪

堆内存参数

  • Xms初始堆大小

  • Xmx最大堆大小

  • Xmn新生代大小

  • XX:SurvivorRatio设置eden区/from(to)的比例

  • XX:NewRatio设置老年代/新生代比例

  • XX:PrintGC/XX:+PrintGCDetails打印GC的过程信息

eg:

/*** 来自于《实战Java虚拟机》* -Xmx20m -Xms20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC* 新生代1M,eden/s0=2, eden 512KB, s0=s1=256KB * 新生代无法容纳1M,所以直接放老年代* * -Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC* 新生代7M,eden/s0=2, eden=3.5M, s0=s1=1.75M* 所以可以容纳几个数组,但是无法容纳所有,因此发生GC* * -Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC* 新生代15M,eden/s0=8, eden=12M, s0=s1=1.5M* * -Xmx20m -Xms20m -XX:NewRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC* 新生代是6.6M,老年代13.3M* @author Tom**/
public class NewSizeDemo {public static void main(String[] args) {      byte[] b = null;for(int i=0;i<10;i++){b = new byte[1*1024*1024];}       }
}

现有垃圾收集器

  • 串行收集器(Serial Collector)

  • 并行收集器(Parallel Collector)

  • ……

注意:本章所学的JVM内存管理和Java并发内存模型不同

第十三章 Java运行管理

1、Java运行管理概述

OS管理

  • 进程级别的管理(黑盒)
  • CPU/内存/IO等具体性能监控

JVM管理

  • 线程/程序级别的管理(白盒)
  • 查看虚拟机运行时各项信息
  • 跟踪程序的执行过程,查看程序运行时信息
  • 限制程序对资源的使用
  • 将内存导出为文件进行具体分析
  • ……
2、OS层管理

Linux平台管理

  • top命令 查看系统中最占资源的部分
  • vmstat命令 查看系统整体的CPU/内存/IO/Swap等信息
  • iostat命令 查看系统详细的IO信息

Windows平台管理

  • 任务管理器
  • perfmon工具(性能监视器)(Process Explorer)
3、JDK管理工具

jdk管理工具

  • 编译/运行工具:javac/java
  • 打包工具:jar
  • 文档工具:javadoc
  • 国际化工具:native2ascii
  • 混合编程工具:javah
  • 反编译工具:javap
  • 程序运行管理工具:jps/jstat/jinfo/jstack/jstatd/jcmd
4、可视化管理工具

可视化Java管理工具

  • JConsole
  • Visual VM
  • Mission Control

JConsole

  • 监管本地Java进程
  • 监管远程Java进程
    • 需要远程进程启动开启JMX服务
  • 静态监控

Visual VM

  • 可以查看、统计,也可以支持插件扩展
  • 动态监控
  • jvisualvm

Mission Control

  • jmc
  • 带有商业化色彩
5、堆文件分析

jmap

  • jdk自带的命令
  • 可以统计堆内对象实例
  • 可以生成堆内存dump文件
6、JMX

JMX

  • Java Management eXtensions
  • JMX是一个为应用程序植入管理功能的框架
  • 用户可以在任何Java应用程序中使用这些代理和服务实现管理

JVM本身就是构架在JMX的基础之上

7、Java运行安全

Java提供安全管理器

  • 对程序的行为进行安全判定,如违反,报错

  • 加载安全策略文件

    • 一个权限集合,包含各种权限,规定哪些程序可以做哪些功能
  • 默认情况下,普通程序不加载安全管理器

  • 启用安全管理器的两种方式

    • System.setSecurityManager(new SecurityManager());
      
    • 加上启动参数

    java -Djava.security.manager -Djava.security.policy=My.policy HelloWorld
    

安全策略文件

  • 建立代码来源和访问权限的关系

  • 代码来源

    • 代码位置
    • 证书集
  • 权限

    • 由安全管理器负责加载的安全属性
  • 系统默认的安全策略文件

    • %JAVA_HOME%/jre/lib/security/java.policy
      
  • 可以自定义权限类,继承Permission类

第十四章:高阶总结

Spring
  • Spring MVC框架和Spring Boot开发模式
  • 最主要的特点
    • IoC/DI,Inversion of Control/Dependency Injection 一种解决对象耦合的重要方法
    • AOP,Aspect Oriented Programming 一种解决方法耦合的重要方法

Java核心技术(高阶)

  • 着重高阶语法:语法糖、泛型、反射、代理、注解、嵌套类、Lambda、stream等
  • 阐述底层原理:字节码、类加载器、JVM内存管理、Java运行管理等

Java基础学习笔记(三)_Java核心技术(高阶)相关推荐

  1. Java基础学习笔记(二)_Java核心技术(进阶)

    本篇文章的学习资源来自Java学习视频教程:Java核心技术(进阶)_华东师范大学_中国大学MOOC(慕课) 本篇文章的学习笔记即是对Java核心技术课程的总结,也是对自己学习的总结 文章目录 Jav ...

  2. Java基础学习笔记三 Java基础语法

    Scanner类 Scanner类属于引用数据类型,先了解下引用数据类型. 引用数据类型的使用 与定义基本数据类型变量不同,引用数据类型的变量定义及赋值有一个相对固定的步骤或格式. 数据类型 变量名 ...

  3. 尚学堂JAVA基础学习笔记_2/2

    尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...

  4. Python基础学习笔记三

    Python基础学习笔记三 print和import print可以用,分割变量来输出 import copy import copy as co from copy import deepcopy ...

  5. 【Java基础学习笔记】- Day11 - 第四章 引用类型用法总结

    Java基础学习笔记 - Day11 - 第四章 引用类型用法总结 Java基础学习笔记 - Day11 - 第四章 引用类型用法总结 4.1 class作为成员变量 4.2 interface作为成 ...

  6. Java中大数据数组,Java基础学习笔记之数组详解

    摘要:这篇Java开发技术栏目下的"Java基础学习笔记之数组详解",介绍的技术点是"java基础学习笔记.基础学习笔记.Java基础.数组详解.学习笔记.Java&qu ...

  7. Java基础学习笔记(完结)

    Java基础 前言 一.Java基础语法 1.注释 2.标识符 3.数据类型 4.类型转换 5.变量.作用域.常量 6. 运算符 二.Scanner与流程控制 1. Scanner对象 2. 流程控制 ...

  8. 【已完结!】Java基础--学习笔记(零起点打开java世界的大门)--博客汇总表【附:视频、工程源码、资料、详细笔记】

    java零基础入门到精通(2019版)[黑马程序员] 视频+资料(工程源码.笔记)[链接:https://pan.baidu.com/s/1MdFNUADVSFf-lVw3SJRvtg   提取码:z ...

  9. Java基础(学习笔记)

    其他文章链接 Java基础 Java集合 多线程 JVM MySQL Redis docker 计算机网络 操作系统 文章目录 前言 1.⾯向对象和⾯向过程的区别 2.Java 和 C++的共性与区别 ...

最新文章

  1. Java项目:设计管理系统(java+SSM+JSP+MYSQL+layui+Maven)
  2. MairDB 如何查询表
  3. uniapp ajax数据库查询,uniapp小程序登录、数据请求方式
  4. 漫画 | 小白也能看懂的量子物理漫画终于来了!
  5. Linux安全驱动模块,【漏洞预警】CVE-2017-2636:linux 内核n_hdlc驱动模块 本地提权漏洞...
  6. Linux 下 的 Oracle,如何安装 tnsname
  7. 软件定义重划边界——IT就是把复杂东西简单化
  8. JSK-134 求出现次数最多的字符【入门】
  9. atitit.文件上传带进度条的实现原理and组件选型and最佳实践总结O7
  10. matlab曲线拟合幅频特性曲线_频域稳定性与matlab指令计算
  11. 微信小程序资源汇总整理
  12. 继承关系下怎样使用Builder 模式
  13. 舱机器人尾巴毛茸茸_并无卵用的毛茸茸机器人 如此呆萌 设计是为了爱
  14. 苹果宣布前CEO史蒂夫·乔布斯逝世 世上再无乔布斯!
  15. 买了服务器,自己怎么搭建网站?操作步骤
  16. 墙裂推荐,2023年最强、最实用的IDEA插件推荐合集
  17. 计算机打音乐两只老虎,两只老虎(音乐、汇编程序)
  18. 学习记录:使用STM32F1看门狗
  19. Paxos Made Practical
  20. 优恩解答GDT放电管是怎么工作的

热门文章

  1. Fiddler的详细介绍
  2. 计算机输入法无法启动,win7电脑开机没有输入法怎么办?
  3. 银行数据治理的一些思考(不限于银行 数据治理)
  4. 一只能看懂表格图片的数据助手
  5. 从 EuRoC MAV Dataset 的 .zip 文件生成 .bag 的 python 脚本
  6. 解析:外部网页内如何一键复制微信号添加微信好友
  7. 纽约大学计算机工程专业课程,NYU的Electrical and Computer Engineering「纽约大学电气与计算机工程系」...
  8. 8.合并两个有序的数组
  9. UCAS计算机网络实验
  10. 怎么在抖音中一键复制微信号打开微信引流