Java知识整理(陆续更新)

第一篇文章,最近再系统的复习Java知识,还打算学习一下大数据相关的东西,好记性不如烂笔头,做个记录时常翻看一下,Java这块主要参照了ThinkWon的一篇很详细的总结,我在这位前辈的基础上做了一些自己的扩展,如果有什么不对的地方欢迎指正

Java

Java诞生背景

1990年初步诞生,由高斯林和舍林丹等人开发,起名OaK(橡树),定位主要用于家电等小型系统的通信和控制,改造之后于1995年5月以Java正式命名发布

Java三大版本

Java SE 标准版

JDK的根基,用来开发C/S(Client/Server)架构软件,主要用于开发和部署桌面、服务器、嵌入设备和实时环境中的Java应用程序,例如Java开发工具Eclipse

Java EE 企业版

Java SE的扩展,用来开发B/S(Browser/Server)架构软件,主要针对企业应用的开发。例如电子商务网站、ERP系统

Java ME 微型版

Java SE的扩展,主要针对移动设备软件和嵌入式设备软件。例如可视电话、数字机顶盒、汽车导航系统、手机游戏

Java技术体系图

在这里插入图片描述

JVM、JRE和JDK的关系

JVM

Java Virtual Machine Java虚拟机,Java程序需要运行在虚拟机上,不同平台有自己的虚拟机,所以Java语言可以实现跨平台

JRE

Java Runtime Environment Java运行环境,包含JVM和Java程序所需的核心类库,比如java.long包
如果要运行一个开发好的Java程序,只需要安装JRE即可

JDK

Java Development Kit Java开发工具包,其中包含JRE,安装了JDK就不需要再单独安装JRE,开发工具包含编译工具(javac.exe)打包工具(jar.exe)等

三者关系图

在这里插入图片描述

Java环境变量配置

1.变量名:JAVA_HOME
变量值:C:\Java\jdk1.8.0_191 (JDK安装目录)
2.变量名:Path
变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
3.变量名:CLASSPATH
变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar

二进制

原码反码补码

原码

以 int 类型为例,int类型占4个字节、共32位。
例如:
2 的原码为:00000000 00000000 00000000 00000010
-2的原码为:10000000 00000000 00000000 00000010

反码

正数的反码:与原码相同
负数的反码:原码的符号位不变,其他位取反
例如,-2 的反码为:11111111 11111111 11111111 11111101

补码

正数的补码:与原码相同
负数的补码:反码+1
例如,-2 的补码为:11111111 11111111 11111111 11111110

注意事项

1.二进制的最高位是符号位(“0”代表正数,“1”代表负数);
2.Java中没有无符号数;
3.计算机以整数的补码进行运算;

成员变量、静态变量和局部变量的区别

1.作用域
成员变量:整个类有效
静态变量:所有类共享
局部变量:某个范围内有效(方法或语句体)

2.存储位置
成员变量:堆内存
静态变量:存放在方法区的静态区
局部变量:栈内存

3.生命周期
成员变量:随着对象存在而存在,随着对象消失而消失
静态变量:随着类的加载而存在,随着类的消失而消失
局部变量:方法被调用或语句被执行是创建,方法调用完或语句结束后自动释放

4.初始值
成员变量:有默认初始值
静态变量:有默认初始值
局部变量:没有默认初始值,使用前必须赋值

计算机存储单元

计算机存储设备的最小信息单元是位,用b(bite)表示,最小的存储单元是字节,用B(byte)表示,字节由连续的8个位组成

Java基本数据类型

在这里插入图片描述

Java反射

定义

Java反射机制是在运行状态中,对任意一个类都能知道这个类的所有属性和方法,对任意一个对象都能调用这个对象所有的属性和方法。这种动态获取信息和调用对象属性和方法的功能称为Java语言的反射机制

用途

通过反射,Java代码可以发现有关已加载类的字段,方法和构造函数的信息,并可以在安全限制内对这些字段方法和构造函数进行操作。
在使用开发工具(例如IDEA/Eclipse)时,属入一个对象或类+“.”编译器会自动提示它的属性或方法,就是用到了反射。
反射最主要的用途是开发各种通用的框架,例如Spring通过XML配置模式装载Bean

优缺点

优点

使用反射,代码可以在运行时装配,提高了灵活性和扩展性和自适应能力,降低了耦合性。它允许程序创建和控制任何类的对象,无需硬编码目标类

缺点

1.性能问题:使用反射基本是一种解释操作,JVN无法对这些代码进行优化,所以反射操作的效率要比非反射操作低得多。反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,对性能要求高的的程序不建议使用
2.安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行
3.内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法)所以在使用反射时可能会导致意料之外的副作用----代码有功能上的错误,降低可移植性,反射代码破坏了抽象性,因此当平台发生变化时代码的行为也可能随之变化

反射获取Class对象的三种方式

一:使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。但可能抛出 ClassNotFoundException 异常
Class<?> clz= Class.forName(“java.lang.String”);

二:这种方法只适合在编译前就知道操作的 Class。直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高,这说明任何一个类都有一个隐含的静态成员变量 class
Class stringClass = String.class;

三:使用类对象的 getClass() 方法。
String a = “a”;
Class<? extends String> aClass1 = a.getClass();

反射机制的相关类

Class类

Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。

获得类相关的方法
方法用途说明
asSubclass(Class clazz)把传递的类的对象转换成代表其子类的对象调用方Class是传入参数Class的子类(extends和implements都可)时正常执行,返回调用方Class,否则抛出异常:java.lang.ClassCastException
cast(Object o)把对象转换成代表类或是接口的对象调用方Class是传入参数对象的子类(extends和implements都可)时正常执行,返回传入参数对象,否则抛出异常:java.lang.ClassCastException
getClassLoader()获得类的加载器关于类加载器的理解
getClasses()返回一个数组,数组中包含该类中所有公共类和接口类的对象子类调用会同时得到子类自己和父类的公共类和接口类
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象子类调用只会得到子类自己所有类和接口类
forName(String className)根据类名返回类的对象类加载的方式对比
getName()获得类的完整路径名字-
newInstance()创建类的实例-
getPackage()获得类的包-
getSimpleName()获得类的名字-
getSuperclass()获得当前类继承的父类的名字-
getInterfaces()获得当前类实现接口-
获得类中属性相关的方法
方法用途说明
getField(String name)获得某个公有的属性对象可以获取自己的和继承的类的以及实现的接口的公共的属性
getFields()获得所有公有的属性对象会同时得到自己的和继承的类的以及实现的接口的所有公共的属性
getDeclaredField(String name)获得某个属性对象只能获取该类自己的,不能获取继承的父类或者实现的接口类的
getDeclaredFields()获得所有属性对象会同时得到自己的和实现的接口的所有的属性,获取不到继承的父类的属性
获得类中注解相关的方法(获取类中的注解,不包括方法和属性上的)
方法用途说明
getAnnotation(Class annotationClass)返回该类中与参数类型匹配的公有注解对象可以获取到自己和父类的,父类的注解须可继承才能获取到,AnnotationType.getInstance(Class.forName(“xxx.xxx.xxx”)).isInherited()是true时该注解可继承
getAnnotations()返回该类所有的公有注解对象可以获取到自己和父类的,父类的注解须可继承才能获取到,AnnotationType.getInstance(Class.forName(“xxx.xxx.xxx”)).isInherited()是true时该注解可继承
getDeclaredAnnotation(Class annotationClass)返回该类中与参数类型匹配的所有注解对象只能获取到自己的
getDeclaredAnnotations()返回该类所有的注解对象只能获取到自己的

关于注解的一点扩展,Java自定义注解

获得类中构造器相关的方法
方法用途说明
getConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的公有构造方法-
getConstructors()获得该类的所有公有构造方法-
getDeclaredConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的构造方法-
getDeclaredConstructors()获得该类所有构造方法-
获得类中方法相关的方法
方法用途说明
getMethod(String name, Class…<?> parameterTypes)获得该类某个公有的方法-
getMethods()获得该类所有公有的方法-
getDeclaredMethod(String name, Class…<?> parameterTypes)获得该类某个方法-
getDeclaredMethods()获得该类所有方法-
类中其他重要的方法
方法用途说明
isAnnotation()如果是注解类型则返回true-
isAnnotationPresent(Class<? extends Annotation> annotationClass)如果是指定类型注解类型则返回true-
isAnonymousClass()如果是匿名类则返回true-
isArray()如果是一个数组类则返回true-
isEnum()如果是枚举类则返回true-
isInstance(Object obj)如果obj是该类的实例则返回true-
isInterface()如果是接口类则返回true-
isLocalClass()如果是局部类则返回true-
isMemberClass()如果是内部类则返回true-
Field类

Field代表类的成员变量(成员变量也称为类的属性)。

方法用途说明
equals(Object obj)属性与obj相等则返回true-
get(Object obj)获得obj中对应的属性值-
set(Object obj, Object value)设置obj中对应属性值-
Method类

Method代表类的方法。

方法用途说明
invoke(Object obj, Object… args)传递object对象及参数调用该对象对应的方法-
Constructor类

Constructor代表类的构造方法。

方法用途说明
newInstance(Object… initargs)根据传递的参数创建类的对象-

注意:通过反射获取私有属性,方法和构造方法时,需要进行暴力反射,设置setAccessible(true)。否则会报错说无法获取私有属性,方法和构造方法

Java语法糖

定义

语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin(彼得·兰丁) 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。语法糖就是对现有语法的一个封装。简而言之,语法糖让程序更加简洁,有更高的可读性。

还有语法盐、语法糖精、语法-海-洛-因
语法盐(是指在计算机语言中为了降低程序员撰写出不良代码的一种设计,但其中仍会有潜藏错误存在的可能。这些特性强迫程序员做出一些基本不用于描述程序行为,而是用来证明他们知道自己在做什么的额外举动。比如强制类型检查)、
语法糖精(也叫语法糖浆,指的是未能让编程更加方便的附加语法,亦说是设计失败的语法糖)、
语法-海-洛-因(是指过于喜欢操作符重载,表面上程序紧凑了,操作符重载就是把已经定义的、有一定功能的操作符进行重新定义,来完成更为细致具体的运算等功能。操作符重载可以将概括性的抽象操作符具体化,便于外部调用而无需知晓内部具体运算过程。但最后程序的可读性可想而知了。)

解语法糖

语法糖的存在主要是方便开发人员使用。但是,Java虚拟机并不支持这些语法糖。Java中的语法糖只存在于编译期,这些语法糖在编译阶段(编译器将 .java 源文件编译成 .class 字节码时)就会被还原成简单的基础语法结构,这个过程就是解语法糖。

糖块一、switch支持String与枚举

在JDK1.7之前,switch语句只支持byte short int char,对于整型直接进行数值比较,对于char则是比较ASCII码
在JDK1.7的时候加入了对String和枚举的支持
写一个switch使用String的实例:

package com.lyh.study.sugar;


import org.junit.Test;

public class TestClass {

    @Test
    public void test1() {

        String a = "a";
        switch(a){
            case "a":
                System.out.println(1);
            case "b":
                System.out.println(2);
        }

    }
}

反编译后得到:

package com.lyh.study.sugar;

import org.junit.Test;

public class TestClass {
    public TestClass() {
    }

    @Test
    public void test1() {
        String a = "a";
        byte var3 = -1;
        switch(a.hashCode()) {
        case 97:
            if(a.equals("a")) {
                var3 = 0;
            }
            break;
        case 98:
            if(a.equals("b")) {
                var3 = 1;
            }
        }

        switch(var3) {
        case 0:
            System.out.println(1);
        case 1:
            System.out.println(2);
        default:
        }
    }
}

通过反编译可以看出,switch对String的比较是先比较hashCode(关于String.hashCode方法的引申)然后使用equals进行安全检查避免发生哈希碰撞

switch使用枚举类型:

@Test
    public void test2() {
        String a = "STRING";
        switch (TestEnum.valueOf(a)) {
            case STRING:
                System.out.println("STRING");
                break;
            case INT:
                System.out.println("INT");
                break;
            case LONG:
                System.out.println("LONG");
                break;
        }

    }

反编译后:

@Test
    public void test2() {
        String a = "STRING";
        switch(null.$SwitchMap$com$lyh$study$sugar$TestClass$TestEnum[TestClass.TestEnum.valueOf(a).ordinal()]) {
        case 1:
            System.out.println("STRING");
            break;
        case 2:
            System.out.println("INT");
            break;
        case 3:
            System.out.println("LONG");
        }

    }

通过反编译可以看出,switch对枚举类型的比较是比较枚举值的序列值

糖块二、泛型和类型擦除

通常情况下,一个编译器处理泛型有两种方式
1.Code specialization。在实例化一个泛型类或方法时都产生一份新的目标代码(字节码或二进制编码)C++编译器使用的就是这种方式,这样会导致代码膨胀。 Code specialization另外一个缺点是在引用类型系统中浪费空间,因为引用类型集合中的元素本质都是一个指针,没有必要为每个类型都生成一份执行代码,这也是Java使用Code sharing处理泛型的主要原因。
2.Code sharing。对每个泛型类只生成唯一一份目标代码,该泛型所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。

类型擦除
通过类型参数合并,将泛型类型实例关联到同一份字节码上。类型擦除的关键在于从泛型类型中清除类型参数,并且在需要的时候添加检查类型和类型转换的方法。
类型擦除的过程
1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
2.移除所有的类型参数
代码:

public interface Comparable<T> {
    int compareTo(T t);
}
public class Number implements Comparable<Number>{
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public int compareTo(Number number) {
        return this.value-number.value;
    }
}

类型擦除后会变成:

public interface Comparable {
    int compareTo(Object t);
}
public class Number implements Comparable{
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
	public int compareTo(Number number) {
        return this.value-number.value;
    }
    @Override
    public int compareTo(Object t) {
        return this.compareTo((Number)t);
    }
}

泛型类Comparable擦除后被替换为最左边界Object。Comparable的类型参数Number被擦除掉,这相当于Number没有实现接口Comparable的compareTo(Object that)方法,于是编译器自动添加了一个桥接方法。
代码:

public class Collections {
    public static <T extends Comparable<T>> T max(Collection<T> xs) {
        Iterator<T> xi = xs.iterator();
        T w = xi.next();
        while (xi.hasNext()) {
            T x = xi.next();
            if (w.compareTo(x) < 0) w = x;
        }
        return w;
    }

    public static void main(String[] args) {
        LinkedList<Number> numberList = new LinkedList<Number>();
        numberList.add(new Number(0));
        numberList.add(new Number(1));
        Number y = Collections.max(numberList);
    }
}

类型擦除后:

public static Comparable max(Collection xs) {
        Iterator xi = xs.iterator();
        Comparable w = (Comparable) xi.next();
        while (xi.hasNext()) {
            Comparable x = (Comparable) xi.next();
            if (w.compareTo(x) < 0) w = x;
        }
        return w;
    }

    public static void main(String[] args) {
        LinkedList<Number> numberList = new LinkedList<Number>();
        numberList.add(new Number(0));
        numberList.add(new Number(1));
        Number y = (Number) Collections.max(numberList);
    }

泛型方法max限定了类型参数的边界<T extends Comparable> T,T必须为 Comparable的子类,按照最左边界将参数替换为Comparable然后去掉参数类型T,得到上边的最终结果

类型擦除带来的问题
1.

 public class Erasure{

            public void test(List<String> ls){
                System.out.println("Sting");
            }
            public void test(List<Integer> li){
                System.out.println("Integer");
            }
    }

该代码编译时报错,在这里插入图片描述
因为类型擦除后他们就是一样的了,同理如果我们自定义了一个泛型异常类,不要同时去catch它的多个实例
2.泛型类的静态变量是共享的

public class TestDemo<T> {
    private static int a = 0;

    public static int getA() {
        return a;
    }

    public static void setA(int a) {
        TestDemo.a = a;
    }

    public TestDemo() {
    }

    public static void main(String[] args) {
        TestDemo<Integer> t = new TestDemo<Integer>();
        t.setA(666);
        TestDemo<String> t2 = new TestDemo<String>();
        t2.setA(888);
        System.out.println(t.getA());
    }
}

最终输出结果是888

糖块三、自动装箱与拆箱

Java自动将原始类型值转换成对应的对象的过程叫做自动装箱,例如将int转换成Integer,反之就叫自动拆箱,例如将Ingeter转换成int
自动装箱的代码:

@Test
    public void test4() {
        int a = 1;
        Integer b = a;
        System.out.println(b);
    }

反编译之后:

@Test
    public void test4() {
        int a = 1;
        Integer b = Integer.valueOf(a);
        System.out.println(b);
    }

自动拆箱代码:

@Test
    public void test5() {
        Integer b = 1;
        int a = b;
        System.out.println(a);
    }

反编译后:

@Test
    public void test5() {
        Integer b = Integer.valueOf(1);
        int a = b.intValue();
        System.out.println(a);
    }

注意:

  • 在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓>存和重用。
  • 适用于整数值区间-128 至 +127。
  • 只适用于自动装箱。使用构造函数创建对象不适用。
	public static void main(String[] args) {
        Integer a = 1000;
        Integer b = 1000;
        Integer c = 10;
        Integer d = 10;
        System.out.println("a==b "+(a==b));
        System.out.println("c==d "+(c==d));
    }

执行结果:
在这里插入图片描述

糖块四 、方法变长参数

定义一个接收可变长参数的方法:

public void print(String... a){
        for(String s : a){
            System.out.println(s);
        }
    }

    @Test
    public void test6() {
        print("啦啦啦","啦啦啦","我是卖报的小画家","大风大雨的满街跑");
    }

反编译之后:

public void print(String... a) {
        String[] var2 = a;
        int var3 = a.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String s = var2[var4];
            System.out.println(s);
        }

    }

    @Test
    public void test6() {
        this.print(new String[]{"啦啦啦", "啦啦啦", "我是卖报的小画家", "大风大雨的满街跑"});
    }

可以看出,可变长参数被转换成一个长度为参数个数的数组进行传递

糖块五 、枚举

Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。
写一个简单的枚举:

public enum Time {
    HOUR,MINUTE,SECOND;
}

反编译之后:

public final class Time extends Enum {
    public static final /* enum */ Time HOUR = new Time("HOUR", 0);
    public static final /* enum */ Time MINUTE = new Time("MINUTE", 1);
    public static final /* enum */ Time SECOND = new Time("SECOND", 2);
    private static final /* synthetic */ Time[] $VALUES;

    public static Time[] values() {
        return (Time[])$VALUES.clone();
    }

    public static Time valueOf(String name) {
        return  (Time)Enum.valueOf(Time.class, name);
    }

    private Time(String string, int n) {
        super(string, n);
    }

    static {
        $VALUES = new Time[]{HOUR, MINUTE, SECOND};
    }
}

我们可以看到,其实它是一个public final class并且继承了Enum,因为有final关键字修饰所以枚举类不能被继承。

糖块六 、内部类

内部类又称嵌套类,也可以把它理解为外部类的一个普通成员,它之所以也是语法糖,是因为它只是一个编译时的概念,定义一个内部类:

public class OutClass {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {

    }

    class InnerClass {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

}

编译后会生成两个class文件:
在这里插入图片描述

糖块七 、条件编译

public static void main(String[] args) {
        if(true){
            System.out.println("true");
        }
        if(false){
            System.out.println("false");
        }
    }

反编译之后:

public static void main(String[] args) {
        System.out.println("true");
    }

这里并没有出现System.out.println("false");这就是条件编译,所以,Java语法的条件编译,是通过判断条件为常量的if语句实现的。根据if判断条件的真假,编译器直接把分支为false的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个Java类的结构或者类的属性上进行条件编译。

糖块八 、断言

在Java中,assert关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略)。
编写一段包含断言的代码:

public static void main(String[] args) {
        int a = 1;
        int b = 1;
        assert a == b;
        System.out.println("true");
        assert a != b : "false";
        System.out.println("last");
    }

执行结果:
在这里插入图片描述
开启断言,需要用开关-enableassertions或-ea来开启,IDEA的开启方法:
在这里插入图片描述
再次执行:
在这里插入图片描述
反编译之后:

public class OutClass{
	static final /* synthetic */ boolean $assertionsDisabled;

    public static void main(String[] args) {
        boolean a = true;
        boolean b = true;
        if (!$assertionsDisabled && a != b) {
            throw new AssertionError();
        }
        System.out.println("true");
        if (!$assertionsDisabled && a == b) {
            throw new AssertionError((Object)"false");
        }
        System.out.println("last");
    }

    static {
        $assertionsDisabled = !OutClass.class.desiredAssertionStatus();
    }

}

断言的底层实现就是if语言,如果断言结果为true,则什么都不做,程序继续执行,如果断言结果为false,则程序抛出AssertError来打断程序的执行。

-enableassertions会设置$assertionsDisabled字段的值。

糖块九 、数值字面量

在java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。
如:

public static void main(String[] args) {
        int c = 90_00_0;
    }

反编译后:

public static void main(String[] args) {
        int c = 90000;
    }

反编译后就是把删除了_。也就是说编译器并不认识在数字字面量中的_,需要在编译阶段把他去掉。

糖块十 、增强for循环

for-each日常开发经常用到:

public static void main(String[] args) {
        String[] strings = new String[]{"12345","上山打老虎","老虎没打到","见到小松鼠"};
        for(String s : strings){
            System.out.println(s);
        }

        List<String> list = new ArrayList<>();
        list.add("两只老虎~两只老虎");
        list.add("谈恋爱~谈恋爱");
        list.add("两只都是公的~两只都是公的");
        list.add("真变态~真变态");
        for(String s : list){
            System.out.println(s);
        }
    }

反编译之后:

public static void main(String[] args) {
        String[] strings = new String[]{"12345", "上山打老虎", "老虎没打到", "见到小松鼠"};
        String[] list = strings;
        int var4 = strings.length;

        for(int s = 0; s < var4; ++s) {
            String s1 = list[s];
            System.out.println(s1);
        }

        ArrayList var7 = new ArrayList();
        var7.add("两只老虎~两只老虎");
        var7.add("谈恋爱~谈恋爱");
        var7.add("两只都是公的~两只都是公的");
        var7.add("真变态~真变态");
        Iterator var8 = var7.iterator();

        while(var8.hasNext()) {
            String var9 = (String)var8.next();
            System.out.println(var9);
        }

    }

for-each的实现原理其实就是使用了普通的for循环和迭代器。也因此要注意一个问题:

for(Eagle i : list){
        if(true){
            list.remove(i);
        }
}

会抛异常

在这里插入图片描述
Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。

所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法remove()来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

糖块十一 、try-with-resource语句

Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。关闭资源的常用方式就是在finally块里调用close方法。比如,我们经常会写这样的代码:

public static void main(String args[]) {
    BufferedReader br = null;
    try {
        String line;
        br = new BufferedReader(new FileReader("d:\\hello.xml"));
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    } finally {
        try {
            if (br != null) {
                br.close();
            }
        } catch (IOException ex) {
            // handle exception
        }
    }
}

使用try-with-resource语句:

try (BufferedReader brn = new BufferedReader(new FileReader("D:\\a.txt"))) {
            String line;
            while ((line = brn.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

看上去简洁了很,反编译之后:

try {
            BufferedReader e1 = new BufferedReader(new FileReader("D:\\a.txt"));
            Throwable var3 = null;

            try {
                String line;
                try {
                    while((line = e1.readLine()) != null) {
                        System.out.println(line);
                    }
                } catch (Throwable var28) {
                    var3 = var28;
                    throw var28;
                }
            } finally {
                if(e1 != null) {
                    if(var3 != null) {
                        try {
                            e1.close();
                        } catch (Throwable var27) {
                            var3.addSuppressed(var27);
                        }
                    } else {
                        e1.close();
                    }
                }

            }
        } catch (IOException var30) {
            var30.printStackTrace();
        }

从反编译结果可以看出来是编译器帮我们做了我们没做的关闭资源的操作,也再次印证了语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。

糖块十二、Lambda表达式

Lambda表达式:https://blog.csdn.net/weixin_45661382/article/details/106014154

先写一个简单的例子:

List<String> list = new ArrayList<>();
        list.add("两只老虎~两只老虎");
        list.add("谈恋爱~谈恋爱");
        list.add("两只都是公的~两只都是公的");
        list.add("真变态~真变态");
        list.forEach(s -> System.out.println(s));

反编译后:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
        list.add("两只老虎~两只老虎");
        list.add("谈恋爱~谈恋爱");
        list.add("两只都是公的~两只都是公的");
        list.add("真变态~真变态");
    list.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}

private static /* synthetic */ void lambda$main$0(String s) {
    System.out.println(s);
}

可以看到,在forEach方法中,其实是调用了java.lang.invoke.LambdaMetafactory#metafactory方法,该方法的第四个参数implMethod指定了方法实现。可以看到这里其实是调用了一个lambda$main$0方法进行了输出。

再写一个对list过滤的例子:

List<String> list = new ArrayList<>();
        list.add("两只老虎~两只老虎");
        list.add("谈恋爱~谈恋爱");
        list.add("两只都是公的~两只都是公的");
        list.add("真变态~真变态");
        List<String> filterList = list.stream().filter(string -> string.contains("老虎")).collect(Collectors.toList());
        filterList.forEach(s -> System.out.println(s));

反编译后:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
        list.add("两只老虎~两只老虎");
        list.add("谈恋爱~谈恋爱");
        list.add("两只都是公的~两只都是公的");
        list.add("真变态~真变态");
    List<Object> filterList = list.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());
    filterList .forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)());
}

private static /* synthetic */ void lambda$main$1(Object s) {
    System.out.println(s);
}

private static /* synthetic */ boolean lambda$main$0(String string) {
    return string.contains("老虎");
}

两个Labmda表达式分别调用了lambda$main1和lambda1和lambdamain$0两个方法。

所以,Labmda表达式的实现其实是依赖了一些底层的api,在编译阶段,编译器会把Labmda表达式进行解糖,转换成调用内部api的方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值