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

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

文章目录

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));  //拆箱再进行数值比较	true

System.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 error
        array2[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();

image-20201221195310533

序列化和反序列化实例:

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());
  • 成员变量、方法、构造函数、修饰符、包、父类、父接口……

image-20201221205350844

注:

  • 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、代理模式和静态代理

image-20201223153802318

代理(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、动态代理

动态代理

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

image-20201223161610917

image-20201223161723786

代理处理器

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

eg:

ProxyHandler	//代理处理器
class ProxyHandler implements InvocationHandler{
    private Subject subject;
    public ProxyHandler(Subject subject){
        this.subject = subject;
    }
    
    //此函数在代理对象调用任何一个方法时都会被调用。
    @Override
    public 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;
    }
    
    //此函数在代理对象调用任何一个方法时都会被调用。
    @Override
    public 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而不是Proxy
        Class 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的补充

image-20201223171107475image-20201223171116158

面向切面编程优点:

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

image-20201223171627570

第五章 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、嵌套类对比

image-20201224203533208

image-20201224204036867

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包中
  • 常用的函数式接口

image-20201225135536539

函数式接口方法
predicatetest
consumeraccept
supplierget
functionapply

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,5

IntStream 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); //abc

Optional<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. 一个流,一次只能一个用途,不能多个用途,用了之后不能再用

    image-202012261517006

  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字节码文件构成

image-20201227150233132

image-20201227152057741

image-20201227151621550

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),程序自定义

image-20201228144008291

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

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概述

image-20201229163927980

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语言并不要求常量一定只有在编译期产生

image-20201229173418241

3、JVM内存参数

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

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

JVM内置有垃圾收集器

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

垃圾收集器的工作职责

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

Java对象的生命周期

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

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

  • 零引用,互引用等

Java四种对象引用:

image-20201229201443214

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

5、垃圾收集算法

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

1、引用计数法

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

2、标记-清除

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

3、复制算法

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

4、标记-整理

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

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

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

image-20201229204720788

image-20201229204753618

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

堆内存参数

  • Xms初始堆大小

  • Xmx最大堆大小

  • Xmn新生代大小

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

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

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

image-20201229211426487

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运行管理概述

image-20201230164127046

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运行管理等
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值