javaee基础

编译性语言和解释性语言

编译性语言:通过编译器把程序编译成为可执行文件,再由机器运行这个文件,运行时不需要重新翻译,直接使用编译的结果就行了。代表语言有C、C++、Golang、汇编等。 解释性语言:边执行一边转换的,其不会由源代码生成可执行文件,而是先翻译成中间代码,再由解释器对中间代码进行解释运行,每执行一次都要翻译一次。代表语言有JavaScript、Python、PHP、Shell等。

java运行机制简介

java JVM java虚拟机 跨平台的关键,在不同的电脑系统中用在jvm中执行java .java-->.class(javac.exe编译)-->java.exe运行在不同系统中的JVM上

JDK JRE

JDK(Java Development Kit) Java开发工具包 JDK = JRE+java的开发工具[java,javac,javadoc,javap等]

JRE(Java Runtime Environment) Java运行环境 JRE=JVM+Java的核心类库[类]

简单的DOS命令

DOS md/mkdir创建文件夹 rd删除文件夹 dir查看当前文件夹中的文件

不同进制以及相互转换

四种进制 二进制 0b开头 例:1110=14 十进制 无 例:14=14
八进制 无 例:16=14 十六进制 0x开头 例: E=14

二进制转八进制 每三个一组 转成对应的八进制数即可 八转二 每一位数 转成三位二进制

二进制转十六进制 每四个一组 转成对应的十六进制即可 十六转八 每一位数 转成四位的二进制

原码、反码、补码

  1. 二进制位是符号为:0表示正 1表示负

  2. 正数的原码,反码,补码都一样(三码合一)

  3. 负数的反码=其原码符号为不变 其他为取反0->1,1->0

  4. 负数的补码=其反码+1 负数的反码=负数的补码-1

  5. 0的反码和补码都是0

  6. java没有无符号数,换言之java中的数都是有符号的

  7. 在计算机运算过程中,都是以补码的方式运算的

  8. 当我们看运算结果的时候,要看其原码

例子:

public class Test {
    public static void main(String[] args) {
        //1. 先的到-2的原码 10000000 00000000 00000000 00000010
        //2. -2的反码       11111111 11111111 11111111 11111101
        //3. -2的补码       11111111 11111111 11111111 11111110
        //4. ~-2操作        00000000 00000000 00000000 00000001 => 1
        System.out.println(~-2);

        //1. 得到2的原码   00000000 00000000 00000000 00000010
        //2. 2的补码=原码  00000000 00000000 00000000 00000010
        //3. ~2的操作      11111111 11111111 11111111 11111101 运算后的补码
        //4. 运算后的反码  11111111 11111111 11111111 11111100 运算后的补码-1
        //5. 运算后的原码  10000000 00000000 00000000 00000011 => -3
        System.out.println(~2);
    }
}

java数据类型

  1. 基本数据类型

    • 整数类型: long、int、short、byte

    • 浮点类型: float、double

    • 字符类型: char

    • 布尔类型: boolean

  2. 引用数据类型 引用数据类型非常多,大致包括:类Class、 接口类型Inteface、 数组类型Array、 枚举类型Enum、 注解类型、 字符串型 例如,String类型就是引用类型。 简单来说,所有的非基本数据类型都是引用数据类型

常用包

java.lang 基本包,默认引用,不需要在引入 java.util 系统提供的工具包,工具类,使用Scanner java.net 网络报,万能滚落开发 java.awt java的界面开发,GUI

## 访问修饰符 public 公开 protected 保护(不同包不可访问) 没有修饰符 向同一个包的类公开(子类 不同包 不可以访问) private 只有类本身可以访问

面向对象编程的三大特征(封装 继承 多态)

封装:将抽象出的数据(属性)和对数据的操作(方法)封装在一起,程序的其他部分只有通过被授权的操作(方法)才能对数据进行操作 好处:

  1. 隐藏实现的细节

  2. 可以对数据进行验证,保证安全合理 封装步骤:

  3. 设置private

  4. 提供公共的set和get方法

继承:解决代码复用

  1. 子类会自动拥有父类定义的属性和方法

  2. 父类又叫 超类,基类

  3. 子类又叫 派生类

注意:当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去掉用弗雷德无参构造器。在子类构造其中默认存在一个super()

继承本质:在内存当中,当子类对象创建好后,建立查找的关系

多态:同一个行为具有不同的表现形式 本质:父类的引用指向子类的对象 多态的具体表现形式:

  1. 方法的多态-->重写和重载体现多态

  2. 对象的多态

    1. 一个对象的编译类型和运行类型可以不一致

    2. 便以类型在定义对象是,就确定了,不能更改

    3. 运行类型是可以变化的

    4. 编译类型看定义时看=左边 运行类型看=右边 编译(javac)时看编译类型,运行方法时(java)运行类型(先后顺序) 特点:

  3. 可以调用父类中的所有成员(方法和对象)

  4. 不能调用子类中特有的成员 例子:

    Father father = new Son();
    father.sonSay()//错误 编译类型为Father,Father中不存在sonSay()方法
    father.say()//如果是父类存在的方法,会按照运行类型进行查找先查找son中的say()如果存在则调用son中的say()不存在调用父类的方法
    
  5. 最终运行效果看子类的具体实现

java的动态绑定机制

  1. 当掉用对象的方法时,该方法会和对象的内存地址/运行类型绑定

  2. 当调用对象的属性时,没有动态更绑定机制,哪里申明,哪里使用

hashCode 哈希值是通过地址计算而来,不等价于地址

Object类--->Finalize 在释放资源之前会执行的方法

Son son = new Son(1);
        son=null;
        System.gc();
@Override
    protected void finalize() throws Throwable {
        System.out.println("释放了资源");
        super.finalize();
    }

静态变量/类变量

  1. 类变量可以通过 类名/对象.*** 方式使用

  2. 类变量可以被同一个类的不同对象同时访问

  3. 在JDK8之前 类变量是在加载类时在方法区创建一个静态域 在静态域当中储存类变量

  4. 在JDK8及之后 是通过在堆中创建一个公有的class对象,通过方法区中定义的类对应堆中的创建的class,每一个类都会在推中生成一个class的实例

静态方法/类方法

与类变量类似 当我么希望不创建实例,也可以调用某个方法(当作工具类),这是把方法做成静态方法非常合适

注意事项:

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区

  2. 类方法中无this的参数

  3. 类方法可以通过对象或类直接调用

  4. 普通方法只能通过类创建的对象调用

理解main方法语法

  1. main方法是通过java虚拟机的调用,

  2. java虚拟机调用不再同一个类,所以该方法访问权限必须是public

  3. java虚拟机在执行main()方法的时候不必创建对象,所以该方法必须是static

  4. 该方法接收String类型的数组参数,该书组中保存执行java命令时传递给所运行的累的参数,案例演示,接收参数

  5. cmd中 编译完后 java 执行的程序 参数1 参数2 参数3 ...... 这些参数存储到String[] args当中

  6. 在idea当中调用args

## 代码块 定义:又叫初始块 只有方法体,在类加载时或对象创建时调用 调用任何一个构造器之前会先制性代码快当中的内容

    {
        System.out.println("构造器构造之前代码块先运行");
    }

静态代码块 加上static之后代码块当中的内容只会被创建一次

类什么时候被加载

  1. 创建对象实例时(new)

  2. 创建子类对象实例,父类也会被加载

  3. 实用类的静态成员时(静态属性 静态方法)前提是该代码块是静态代码块

单例模式

单例模式常见的方式

  1. 饿汉式 1 将构造器私有化 2. 在类的内部直接创建 3. 提供一个公有的static方法,返回对象

public class Test {
    public static void main(String[] args) {
        A a = A.getInstance();
        System.out.println(a.a);
    }
}
class A{
    int a;
    private static A aobj = new A(1);
    private A(int a){
        this.a = a;
    }
    public static A getInstance(){
        return aobj;
    }
}
  1. 懒汉式

    1. 将构造器私有化

    2. 定义一个static静态属性对象

    3. 提供一个公有的static方法,返回对象(在该方法内进行对象赋值)

public class Test {
    public static void main(String[] args) {
        A a = A.getInstance();
        System.out.println(a.a);
    }
}
class A{
    int a;
    private static A aobj;
    private A(int a){
        this.a = a;
    }
    public static A getInstance(){
        if (aobj == null){
            aobj = new A(2);
        }
        return aobj;
    }
}

小结: 1. 单例模式的两种方式 饿汉式 懒汉式 2. 饿汉式问题:在类加载时就创建,可能存在资源浪费的问题 3. 懒汉式问题:存在线程安全的问题

## 抽象类 抽象类中可以不存在抽象方法 有抽象方法存在的类一定是抽象类 想要调用抽象类就必须继承该类 抽象方法不能使用private、final、static方法修饰 抽象类 OOP方法的体现

public class TestMain {
    public static void main(String[] args) {
        Worker worker =new Worker();
        worker.calculate();

        Employee employee = new Employee();
        employee.calculate();
    }
}
abstract class AbstractTest {
    abstract public void work();

    public void calculate() {
        long began = System.currentTimeMillis();
        work();
        long end = System.currentTimeMillis();
        long count = end-began;
        System.out.println("花费时间:"+count);
    }
}
class Employee extends AbstractTest {
    @Override
    public void work() {
        int p=0;
        for (int i = 0; i < 999999990; i++) {
            p+=i;
        }
    }
}
class Worker extends AbstractTest{

    @Override
    public void work() {
        int num=0;
        for (int i = 0; i < 100000000; i++) {
            num+=i;
        }
    }
}

接口

好处:方法名统一,有助于项目分工 给出一些没有实现的方法,封装到一起,到某个类使用的时候,根据不同的情况实现该方法

在jdk7.0前 接口里所有方法都没有方法体 在jdk8.0之后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的实现 但是需要default关键字修饰

注意事项:

  1. 接口类中的属性智能使用final关键字定义 访问方式:接口名.属性

  2. 接口不能继承其他类但可以实现多个别的接口

  3. 接口中的方法默认都是抽象方法,abstract关键字可以省略

  4. 抽象类实现方法可以不实现接口的方法

  5. 接口类可以创建实现类对象 接口类 名 = new 实现类() 体现了接口的多态

  6. 接口可以传递 A实现接口I 如果B继承A 则B也实现接口I

public class MainTest {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.work(new Phone());
        computer.work(new Camera());
    }
}

interface USBInterface {
    public void run();
    public void stop();

    static public void start(){
        System.out.println("开机");
    }

    default public void turnOff(){
        System.out.println("关机");
    }
}

class Camera implements USBInterface{

    @Override
    public void run() {
        System.out.println("camera running");
    }

    @Override
    public void stop() {
        System.out.println("camera stopped");
    }
}

class Phone implements USBInterface{

    @Override
    public void run() {
        System.out.println("phone run");
    }

    @Override
    public void stop() {
        System.out.println("phone stop");
    }
}
class Computer {
    public Computer(){}
    public static void work(USBInterface usbInterface){
        USBInterface.start();
        usbInterface.run();
        usbInterface.stop();
        usbInterface.turnOff();
    }
}

实现接口和继承类 子类继承父类,自动继承了父类的方法,如果子类需要扩展功能,可以通过实现接口的方式进行扩展, 可以理解为:实现接口式对java单继承机制的一种补充

解决问题不同: 继承:解决代码的复用性和可维护性 接口:设计规范化,在一定程度上在代码上解耦

接口比继承更灵活:继承只有单继承,接口可以多个接口一起实现

内部类

定义:一个类的内部有完整的嵌套了另一个类结构,被嵌套的类为内部类 类的五大成员:属性、代码块、构造器、方法、内部类 特点:可以直接访问私有属性,并且可以体现类与类之间的包含关系

四种内部类: 定义在外部类局部位置上:

  1. 局部内部类(有类名,通常在方法)

    • 可以访问外部类所有成员,包含私有

    • 不能添加访问修饰符,但是可以使用final,让别的类无法继承

    • 作用域仅仅在定义他的方法或代码块中

    • 局部内部类和外部类属性重名时按就近原则,如果内部类想获取外部类属性就要加 外部类名.this.属性名 ---外部类名.this本质上就是外部类对象

   public class InnerTest {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f();//调用f方法同时自动创建了Inner内部类
    }
}
class Outer{
    private int n=100;
    public void f(){
        class Inner{//局部内部类
            public void ff(){
                System.out.println(n);
            }
        }
        Inner inner = new Inner();
        inner.ff();
    }
}


  1. 匿名内部类(没有类名,重点!!!)

    • 本质是内部类

    • 该类没有名字(表面没有类名,实际上系统底层自动赋予了)

    • 同时还是一个对象

    • 匿名内部类的接口实现 此时编译类型时I 运行类型就是匿名内部类

interface I{
    public void say();
}
class C{
    public void say(){
        System.out.println("方法");
    }
}
public class InnerTest{
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f();//调用f方法同时自动创建了Inner内部类
        I i = new I() {//匿名内部类
            @Override
            public void say() {
                System.out.println(1);
            }
        };
        C c = new C(){
            @Override
            public void say(){
               System.out.println(2);  
            }
        };
        i.say();
        c.say();
    }
}
 

定义在外部类的成员位上: 3. 成员内部类(没有static修饰)

public class InnerTest{
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.say();
    }
}
class Outer{
    private int n=100;
    public class Inner{
        public void say(){
            System.out.println("shuo");
        }
    }
}
  1. 静态内部类(有static修饰)

public class InnerTest{
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();
        inner.say();
    }
}
class Outer{
    private int n=100;
    public static class Inner{
        public void say(){
            System.out.println("shuo");
        }
    }
}

枚举类

把具有具体值的对象一个个列举出来的类称为枚举类 步骤:

  1. 将构造器私有化 防止直接new

  2. 去掉set方法 防止数值被修改

  3. 创建static固定对象

  4. 优化 可以加入final修饰符

  5. 枚举对象通常全部使用大写

public class Enumeration01 {
    public static void main(String[] args) {
        Seasson spring = Seasson.SPRING;
        System.out.println(spring.toString());
    }
}
class Seasson{
    private String name;
    private String desc;

    public static final Seasson SPRING = new Seasson("spring","1");
    public static final Seasson SUMMER = new Seasson("summer","2");
    public static final Seasson AUTUMN = new Seasson("autumn","3");
    public static final Seasson WINTER = new Seasson("winter","4");

    private Seasson(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "Seasson{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

使用enum关键词实现枚举 注意事项:

  1. 使用enum关键字的售后 默认汇集成一个Enum类 可以通过javap反编译查看到

  2. SPRING("spring","1");此时调用的时对应的有参构造器

  3. 如果是无参构造器 则可以省略括号

  4. 使用enum创建 枚举对象必须放在枚举类的行首

将类class替换成enum申明其是一个枚举类

public class Enumeration02 {
    public static void main(String[] args) {
        System.out.println(Seasson2.SPRING);
        System.out.println(Seasson2.SUMMER);
        System.out.println(Seasson2.AUTUMN);
        System.out.println(Seasson2.WINTER);
        System.out.println(Seasson2.NONE);
    }
}

enum  Seasson2{
    SPRING("spring","1"),
    SUMMER("summer","2"),
    AUTUMN("autumn","3"),
    WINTER("winter","4"),
    NONE;


    private String name;
    private String desc;

    Seasson2(){
        System.out.println("调用无参构造器");
    }
    private Seasson2(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "Seasson{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

Annotation 注解

三个基本的Annotation @Override 重写父类 只作用于父类 @Deprecated 表示某个程序已经过时 @SuperWarnings 抑制编译器警告

元注解 修饰注解的注解称之为元注解 Retention 注定注解的作用范围(三种 SOURCE,CLASS,RUNTIME)

  • SOURCE 作用在java源代码

  • CLASS 编译器将注解记录在class文件中。java程序运行时不会保留注解

  • RUNTIME 编译器将主记录在class文件之后。运行java程序时,JVM会保留注解 Target 指定注解可以在哪些地方使用 Documented 指定该注解是否会在javadoc体现 Inherited 子类会继承父类注解

异常处理机制

Serializable接口-->Throwable-->1.Error 2.Exception 异常分类: Exception Error -> JVM系统内部错误 Exception -> (RuntimeException)运行时异常 + 编译时异常

throws 和 throw区别 throws 异常处理的一种方式->放在方法申明处->跟着异常类型 throw 手动生成异常对象的关键字->放在方法体中->跟着异常对象

包装类Wrapper

基本数据类型

包装类

boolean

Boolean

char

Character

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

jdk5之前 是手动装箱和拆箱 装箱:基本类型->包装类型 反之为拆箱 jdk5及以后 是自动装箱和拆箱 自动装箱底层调用的是valueOf方法 比如Integer.valueOf()

        //jdk5之前手动装箱和拆箱
        int i = 0;
        Integer integer = new Integer(i);
        Integer integer1 = Integer.valueOf(i);
        
        int i2 = integer.intValue();

        //jdk5之后自动装箱拆箱
        int i3 = 1;
        Integer integer2 = i3;//底层使用的任然是Integer.valueOf(i3)
        Integer integer3 = 2;
        i3 = integer3;//integer3.intValue(integer3)
  Integer a1 = 127;
        Integer b1 = 127;
        System.out.println(a1==b1);
        //true

        Integer a2 = 128;
        Integer b2 = 128;
        System.out.println(a2==b2);

        //false

常用类

String

  1. String 对象保存字符串,也就是一组字符序列

  2. 字符串字符使用Unicode字符编码 一个字符(不区分汉子还是字母)站两个字节

  3. String 类有很多构造器 构造器的重载

    • String s1 = new String()

    • String s2 = new String(String original)

    • String s3 = new String(char[] a)

    • String s4 = new String(char[] a,int startIndex,int count)

    • String s5 = new String(byte[] b)

  4. String类 实现了1.接口Serializable(可以串行化:在网络中传输)2.Comparable(可以比较大小)

  5. String是final类 不能被其他类继承

  6. String 有一个属性private final char value[];用于存放字符串内--->每次修改数据都开辟一个新的空间

public class String01 {
    public static void main(String[] args) {
        String a="hello";
        String b = "world";
        String c = a + b;
        //1.先创建一个StringBuilder sb = StringBuilder()
        //2.执行sb.append("hello");
        //3.sb.append("world");
        //4.String c = sb.toString()
        //最后是c指向堆中的对象sb,其保存了“hellowold” 此时常量池中保存了创建了三个对象
        String d = "helloworld";
        String e = "hello" + "world";
        System.out.println(d==e);

        System.out.println(c==d);
        //此时c!=d 因为c指向堆当中的的sb(虚拟名字),sb指向“helloworld” 而 d直接指向helloworld
    }
}

StringBuffer 是可变长度的,是一个容器 String保存的是字符串常量,改变值就是改变地址 StringBuffer保存的是字符串常量,里面的值可以改变。 StringBuffer是final类 不能被继承 StringBuffer字符内容存在char[] value 每次数组满了再扩容效率远高于String

Date 精确到毫秒,代表特定的时间 SimpleDateFormat:格式和解析日期的类

集合

集合框架体系

集合主要分两组(单列集合Collection,双列集合Map) Collection 接口有两个重要的子接口 List Set 他们的实现子类都是单列集合 Map接口实现子类 是双列集合 键-值

Collection

Collection-->1.List 2.Set-->1.1Vector 1.2ArrayList 1.3LinkedList 2.1TreeSet 2.2 HashSet Collection接口遍历对象的方式1.Iterator 2.增强for循环 Iterator Iterator对象成为迭代器 主要用于遍历Collection集合中的元素 所有实现了Collection接口的集合类都有一个iterator()方法,用于放回一个实现了iterator接口对象,即可以返回一个迭代器 Iterator仅仅用于遍历集合 不存放对象

public class Test {
    static class Book{
     String name;
     int price;

        public Book(String name, int price) {
            this.name = name;
            this.price = price;
        }

        @Override
        public String toString() {
            return "Book{" +
                    "name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    }
    public static void main(String[] args) {
        Collection collection = new ArrayList();

        collection.add(new Book("book1",1));
        collection.add(new Book("book2",2));
        collection.add(new Book("book3",3));
        collection.add(new Book("book4",4));

        //增强for循环遍历 底层任然是迭代器调用了iterator
          for (Object book:collection){
            System.out.println(book);
        }


        Iterator iterator = collection.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //再次遍历需要重置迭代器
        iterator = collection.iterator();
    }
}

List

List接口 是Collection接口的子接口

  1. 元素有序且可重复

  2. 有顺序索引 支持索引查找 入:list.add(1,"xxx") 可以在第一个位置上插入

  3. 常用的有 ArrayList LinkedList Vector

  4. 遍历方式 在Collection基础上可以使用索引遍历

ArrayList ArrayList中维护了一个Object类型的数组elementData transient Object[] elementData//transient 表示瞬间的 意思是该属性不会被序列化 使用无参构造器 则初始elementData容量为0,第一次添加 则扩容elementData为10 如需再次扩容 每次扩容1.5倍 如果使用int有参构造创建 每次扩容为1.5倍 扩容 elementData = Arrays.copyOf(elementData, newCapacity)
Vector Vector 底层也是一个对象数组 protected Object[] elementData; Vector 是线程同步的 即线程安全 Vector类的操作方法带有synchronized 开发过程中需要同步安全使用Vector ,ArrayList线程不安全,但效率较高 扩容机制:如果无参数 默认10 每次扩容2倍 LinkedLIst LinkedLIst 底层维护了一个双向链表 LinkedLIst 中维护了两个属性first和last分别指向 首节点和尾节点 每个节点(Node对象) 里面又维护了prev、next、item三个属性 其中通过prev指向前一个 通过next指向后一个节点 实现双向链表 所以LinkedLIst的元素的添加和删除 不是通过数组相对效率较高 LinkedLIst 线程不安全 ArrayList 和 linkedList 比较 底层结构:可变数组 --- 双向链表 增删效率:较低通过数组扩容 --- 较高通过链表追加 改查效率:较高 --- 较低 在程序中 大部分情况下都是查询 因此使用ArrayList的频率较高

Set接口

Set接口 无序(添加的顺序和取出的顺序不一致,但是是固定的,顺序取决于hash后) 没有索引 因此在遍历时不允许索引遍历(只有Collection中的遍历方法 迭代器 增强for) 不允许重复元素 所以最多只包含一个null 常用的实现类 HashSet和TreeSet

HashSet 实现了Set接口 HashSet实际上是HashMap (数组+单链表+红黑树) 在执行.add()方法后返回一个boolean 添加一个元素时 先得到hash值->准换成索引值 找到存储数据表table 看这个索引位置是否已经存放的元素 如果没有直接加入 如果有,调用equals比,若相同放弃添加,不相同则添加 在java8中 如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认8)并且table大小>=MIN_TREEIFY_CAPACITY = 64 就会进化成红黑树

public class Test {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        System.out.println(set.add("aa"));//T
        System.out.println(set.add("aa"));//F

        System.out.println(set.add(new Person("bb")));//T
        System.out.println(set.add(new Person("bb")));//T

        System.out.println(set.add(new String("cc")));//T
        System.out.println(set.add(new String("cc")));//F

    }
    static class Person{
        String name;

        public Person(String name) {
            this.name = name;
        }
    }
}

HashSet-->HashMap 底层原理

  1. HashSet 底层 HashMap

  2. 添加一个元素时, 先得到hash值-会转成-索引值

  3. 第一次添加时 默认扩容到16 临界值(16) * 加载因子(loadFactor 0.75) = 12 到达临界值开始扩容 防止多个线程同时进入导致错误

  4. 找到存储数据表table,看这个索引位置是否已经存放元素

  5. 如果没有 直接加入

  6. 如果有 先比较地址是否相同 不同 -> 调用equals(可以重写)比较 不同 添加到最后

  7. 在java8中 一条链表默认长度 TREEIFY_THRESHOLD=8 ,table>=MIN_TREEIFY_CAPACITY(64) 就会进化成红黑书

public class HashSetTest {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("python");
        hashSet.add("java");
        System.out.println();

        /*
        * 解读
            1.创建一个HashMap对象
             public HashSet() {
                    map = new HashMap<>();
                }
            2.执行put方法
            public V put(K key, V value) {
                    return putVal(hash(key), key, value, false, true);
            }
            3.执行HashMap中的putVal方法
            *
            * 计算key对应的hash值
            * static final int hash(Object key) {
                int h;
                return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
                }
            *
             final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
                Node<K,V>[] tab; Node<K,V> p; int n, i;
                //table 是 HashMap 的一个数组 类型是Node[]
                //if 语句表示如果当前table是null 或者大小=0
                //就第一次扩容 16个空间
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
                //如果计算后的位置节点指向空 则直接添加上该节点
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                //此时计算后的位置节点内有对象
                else {
                    Node<K,V> e; K k;
                    //如果该对象的hash和传入对象的hash相同 或者 调用equals相同 则原地赋值等价于退出
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    //如果 该对象现在是一颗红黑树 则直接将该节点添加到数中
                    else if (p instanceof TreeNode)
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                    //此时确认该位置上是一个链表
                    else {
                        //进行一个死循环
                        for (int binCount = 0; ; ++binCount) {
                            //如果该数组对应的链表位置的next为空(没有重复),则在后面添加当前节点
                            if ((e = p.next) == null) {
                                p.next = newNode(hash, key, value, null);
                                //添加完毕后 判断当前是否>=8 是否该转变为红黑树
                                //注意:转变为红黑树之前 如果当前数组长度小于64 则先进行扩容
                                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            p = e;
                        }
                    }
                    if (e != null) { // existing mapping for key
                        V oldValue = e.value;
                        if (!onlyIfAbsent || oldValue == null)
                            e.value = value;
                        afterNodeAccess(e);
                        return oldValue;
                    }
                }
                ++modCount;
                if (++size > threshold)
                    resize();
                afterNodeInsertion(evict);
                return null;
            }
        * */
    }
}

TreeSet 使用TreeSet 提供的一个构造器 可以传入一个比较器

public class TreeSet_ {
    public static void main(String[] args) {
        Set treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
//                return ((String)o2).compareTo((String)o1);
                return ((String)o2).length()-((String)o1).length();
            }
        });

        treeSet.add("d");
        treeSet.add("aa");
        treeSet.add("bbb");
        treeSet.add("ccc");

        System.out.println(treeSet);
    }
}

Set接口实现 LinkedHashSet 继承了HashSet 底层是LinkedHashMap 底层维护了一个 数组+双向链表 有序 加入和取出元素的顺序一致 在数组中的节点上设置一个head和tail 每个对象都有pre和after存放前后的节点 通过双向链表实现有序输出

Collection工具类

  1. reverse 反向排序

  2. shuffle 随即排序

  3. sort 自然顺序排序

  4. sort(list,new Comparators()); 自定义排序

  5. swap(list,index1,index2); 指定位置交换

  6. max 返回一个对象

  7. frequency(list,对象) 返回int出现次数

public class Collection_ {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        arrayList.add("tom");
        arrayList.add("kevin");
        arrayList.add("jack");

//        //方向排序
//        Collections.reverse(arrayList);
//        //随机排序
//        for (int i = 0; i < 5; i++) {
//            Collections.shuffle(arrayList);
//            System.out.println(arrayList);
//        }
        //元素自然顺序排序 sort
//        Collections.sort(arrayList);
//        //自定义排序 如按照长度
//              Collections.sort(arrayList, new Comparator() {
//            @Override
//            public int compare(Object o1, Object o2) {
//                return ((String)o1).length()-((String)o2).length();
//            }
//        });
        //指定位置交换
        Collections.swap(arrayList,0,1);

        System.out.println(arrayList);
    }
}

Map

  1. Map与Collection并列存在 用于保存具有映射关系的数据 Key-Value

  2. Map 中的 key和value 可以是任何数据类型 会封装到HashMap<span data-formula="Node 对象中

  3. Map中的 key不允许重复 和HashSet一样

  4. 如果键相同采用替换机制 key不允许重复 value可以重复

  5. key为null可行 但只能存在一个

  6. 通过hash key 将计算后的hash值计算数组中的下标值

  7. 为了方便遍历 创建Map时会创建EntrySet集合 该集合 存放的元素的类型Entry 而一个Entry就有K,V,该键值对指向Node当中 EntrySet<Entry<K,V>> 即 transient Set<Map.Entry<K,V>> entrySet

  8. entrySet 中定义的类型是 Map.Entry 但实际上存放的还是HashMap" aria-hidden="true">Node 这是因为 static class Node<K,V> implements Map.Entry<K,V>

  9. 创建一个EntrySet方便遍历 实际上是Map.Entry 提供了重要的方法 K getNode(); V getVal();

Map-->1.HashMap 2.TreeMap 3.Hashtable

Map遍历

  1. containsKey:查找键是否存在

  2. keySet:获取所有的键

  3. entrySet:获取所有关系K-V

  4. values:获取所有的值

public class HashMapTest {
    public static void main(String[] args) {
        Map map = new HashMap();

//        System.out.println(map.put("b", "22").getClass());
        map.put("c","33");
        map.put("c","333");
        map.put("a","11");
        map.put("d","33");

        Set keySet = map.keySet();
        //1.增强for
        for (Object key:keySet){
            System.out.println(key+"-"+map.get(key));
        }
        //2.iterator
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()){
            Object key = iterator.next();
            System.out.println(key+"-"+map.get(key));
        }
        //第二组 把所有values取出
        //1.增强for 2.迭代器 3.for
        Collection values = map.values();
        for(Object value:values){
            System.out.println(value);
        }
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()){
            Object value = iterator1.next();
            System.out.println(value);
        }
        //第三组 通过EntrySet
        Set entrySet = map.entrySet();
        //增强for
        for (Object entry : entrySet) {
            System.out.println(entry);
            //将entry 转成 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m);
//            System.out.println(m.getKey()+"-"+m.getValue());
        }

        //iterator
        Iterator iterator2 = entrySet.iterator();
        while(iterator2.hasNext()){
            Object next = iterator2.next();
            Map.Entry m = (Map.Entry) next;
            System.out.println(m.getKey()+"-"+m.getValue());
        }

    }
}

HashTable

  1. 存放的元素是键值对 和HashMap同级 实现了Map接口

  2. hashTable 的键和值都不能为null 否则会跑出空指针异常

  3. 使用方法基本和HashMap一样

  4. hashTable 线程安全 hashMap线程不安全

  5. 初始创建大小11 扩容机制 *2+1 threadHoad=0.75

HashTable和HashMap区别

  1. 线程安全 hashTable安全 hashMap不安全

  2. HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类

  3. HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75 HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1即:capacity2+1

  4. 两者计算hash的方法不同 Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模 HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸

TreeMap

public class TreeMap_ {
    @SuppressWarnings("all")
    public static void main(String[] args) {
//        Map treeMap = new TreeMap();
        Map treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String)o2);
//                return ((String)o1).length()-((String)o2).length();
            }
        });
        String a="a";
        String b="b";
        treeMap.put("jack","捷克");
        treeMap.put("tom","汤姆");
        treeMap.put("marry","马力");
        treeMap.put("marha","马哈");

        System.out.println(treeMap);
    }
}

Properties

  1. 该类继承自HashTable类并且实现了Map接口 也是使用一种键值对的形式来保存数据

  2. 他的使用特点和HashTable类似

  3. 该类还可以用于 从xxx.properties文件中 加载数据到Properties类对象 并进行读取和修改

  4. 一般xxx.properties文件作为配置文件

选择集合总结

  1. 先判断存储的类型(一组对象 或 一组键值对)

  2. 一组对象: Collection接口

    • 允许重复:List

      • 增删多:LinkedList (底层维护了一个双向链表)

      • 改查多:ArrayList(底层维护 Object类型的可变数组)

    • 不允许重复:Set

      • 无序:HashSet (底层是HashMao 维护了一个哈希表,即(数组+链表+红黑树))

      • 排序:TreeSet

      • 插入和抽取顺序一致:LinkedHashSet 维护数组+双向链表

  3. 一组键值对:Map

    • 键无序:HashMap(底层:哈希表 jdk7:数组+链表 ,jdk8:数组+链表+红黑树)

    • 键排序:TreeMap

    • 插入和抽取顺序一致: LinkedHashMap

    • 读取文件 Properties

绘图

继承JFrame 相当于窗口 继承JPanel 相当于画笔 继承JPanel后需要重写paint方法 paint方法会被调用的情况 1. 第一次显示时 自动调用 2. 窗口最小化 再最大化 3. 窗口大小发生变化 4. repaint函数被调用

public class MyDraw extends JFrame {

    //定义一个面板
    private MyPanel mp = null;
    public static void main(String[] args) {
        new MyDraw();
    }

    public MyDraw(){
        //初始化面板
        mp = new MyPanel();
        //把面板放入到窗口
        this.add(mp);
        this.setSize(400,300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//程序关闭
        this.setVisible(true);
    }
}
class MyPanel extends JPanel{
    /*
    * 1.第一次显示时 自动调用
    * 2.窗口最小化 再最大化
    * 3.窗口大小发生变化
    * 4.repaint函数被调用
    * */
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.drawOval(10,10,100,100);
    }

}

Graphics 常用方法

public void paint(Graphics g) {
        super.paint(g);
        //画直线 drawLine
        //画矩形 drawRect
        //画椭圆 drawOval
        g.drawOval(10,10,100,100);
        //填充矩形 fillRect
        g.setColor(Color.blue);
        g.fillRect(10,10,100,100);
        //填充椭圆 fillOval
        //画图片 1.获取图片资源(图片放在out中的根目录 jdk8可以使用Panel 11需要使用自己Panel的类名) 2.drawImage画图片
//        Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/default.png"));
        Image image = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/default.png"));
        System.out.println("==============="+image);
        g.drawImage(image,10,10,220,220,this);
        //设置画笔颜色字体
        g.setColor(Color.red);
        g.setFont(new Font("隶书",Font.BOLD,50));
        g.drawString("Hello",100,100);

    }

反射

什么是反射

  1. Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

  2. Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁

反射机制

反射代码实现

public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        P p = new P();
        System.out.println(p.getClass());

        Class cla = Class.forName("com.test.P");
        Object o = cla.newInstance();
        Constructor constructor = cla.getConstructor(String.class,int.class);
        Constructor constructor1 = cla.getConstructor();

        System.out.println(o);
        Field name = cla.getField("name");
        name.set(o,"222");
        System.out.println(name.get(o));
        Method method = cla.getMethod("eat");
        method.invoke(o);
    }
    static class P{
        public String name = "111";
        public int age;

        public P(){

        }
        public P(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public void eat(){
            System.out.println(name);
        }

        @Override
        public String toString() {
            return "P{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

Class类加载

  1. Class也是类 因此继承Objet

  2. Class不是new的,而是系统创建出来的

  3. 类只加载一次

  4. 每个类的实例都会记得自己是由那个Class实例所生成的

  5. 通过Class可以完地得到一个类的完整结构,通过一系列API

  6. Class类存放在堆当中

  7. 类的字节码二进制数据是存放在方法区的 称为类的元数据

  8. 有哪些类型可以加载类? 外部类,接口,数组,注解,线程

类的创建

  1. Class.forName

  2. 类名.class 用于参数传递

  3. Class class = 对象.getClass() 已知某个实例的情况

  4. ClassLoader cl = 对象.getClass().getClassLoader(); Class cla = cl.loadClass("类的全名称")

  5. 基本数据类型可以通过.class获得

  6. 基本数据类型包装类可以通过.TYPE获得 代码部分

public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1.Class.forName
        String cla = "com.test.Test$P";
        Class c1 = Class.forName(cla);
        System.out.println(c1);
        //2.类名.class
        Class c2 = P.class;
        System.out.println(c2);
        //3.对象.getClass() 要求有对象实例
        P p = new P();
        Class c3 = p.getClass();
        System.out.println(c3);
        //4.通过类加载器 ClassLoader
        //(1) 先得到类加载器
        ClassLoader classLoader = p.getClass().getClassLoader();
        //(2) 通过类加载器获得Class对象 cla上面定义的类路径
        Class c4 = classLoader.loadClass(cla);
        System.out.println(c4);
         //5. 基本数据类型使用 .class
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        //6. 基本数据类型的包装类使用 .TYPE
        Class<Integer> type = Integer.TYPE;
        Class<Character> type1 = Character.TYPE;
    }
    static class P{
        public String name;

        public void eat(){
            System.out.println(name+" 在吃");
        }
    }
}

类加载 反射机制是java实现动态语言的关键 也就是通过反射实现类的动态加载

  1. 静态加载:编译时加载相关的类,如果没有就报错 依赖性太强 编译的时候会加载 Dog dog = new Dog()

  2. 动态加载:运行时加载需要的类,如果运行时不佳在则不抱错 降低了依赖性 运行的时候才加载,编译不会出错,运行时才检测错误 Class cla = Class.forName("Dog") 静态加载:当创建对象时 子类被加载时,父类也加载 调用类中的静态成员时 动态加载:通过反射

类加载的五个阶段

  1. 加载阶段 JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、jar包,甚至是网络)转为二进制字节流家在到内存中,并生成一个代表该类的java.lang.Class 对象

  2. 连接阶段

    • 连接阶段-验证

      • 目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求 并且不会危害到其自身安全

      • 包括:文件格式验证、元数据验证、字节码验证和符号引用验证

      • 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施 缩短虚拟机类加载的时间

    • 连接阶段-准备

      • JVM会在阶段对静态变量,分类内存并默认初始化,这些变量所使用的内存都将在方法区中进行分配

      • 使用举例 n1 不是静态变量,不会再加载类的时候加载,因此在准备阶段不分配内存 n2 是静态变量,分类内存n2是默认初始化0不是20 n3 是static final 是常量,一旦复制就不变 n3 = 30 int n1 = 10; static int n2 = 20; static final int n3 = 30;

    • 连接阶段-解析 虚拟机将常量吃内的符号引用替换为直接引用的过程 两个类之间靠符号相互引用 在内存中会使用地址直接引用 而这个转变就靠该阶段的解析

  3. 初始化

    • 到初始阶段 才真正开始执行类中定义的java程序代码 此阶段是执行

    public static void main(String[] args)  {
         //分析类加载过程
         //1.加载P类 生成P的class对象
         //2.连接 num = 0
         //3.初始化阶段  依次自动收集类中的所有静态变量的赋值动作和静态代码代码快中的语句 并进行合并
         /*
         *  client(){
         *       num = 10;
                 System.out.println("p 静态代码快给加载了");
                 num = 30;
         * }
         * 合并:num = 30
         * */
         //        new P();
         System.out.println(P.num);//直接使用类的静态属性 也会导致类的加载
    
     }
     static class P{
         static int num = 10;
         static {
             System.out.println("p 静态代码快给加载了");
             num = 30;
         }
     }
    
    • 虚拟机会保证一个类的

反射获取类的结构信息

  • 第一组:java.lang.Class 类 getName:获取全类名 getSimpleName:获取简单类名 getFields:获取所有public修饰的属性,包含本类以及父类的 getDeclaredFields:获取本类中所有属性 getMethods:获取所有public修饰的方法,包含本类以及父类的 getDeclaredMethods:获取本类中所有方法 getConstructors:获取本类中所有public修饰的构造器 getDeclaredConstructors:获取本类中所有构造器 getPackage:以Package形式返回包信息 getSuperClass:以Class形式返回父类信息 getInterfaces:以Class形式返回接口信息 getAnnotations:以Annotation形式返回注解信息

  • 第二组:java.lang.reflect.Field 类 getName:返回属性名 getModifiers:以 int 形式返回修饰符【说明:默认修饰符是 0,public 是 1 ,private 是 2 ,protected 是 4,static 是 8,final 是 16】 getType:返回该属性对应的类 的 Class 对象

  • 第三组:java.lang.reflect.Method 类 getModifiers():以 int 形式返回修饰符【说明:默认修饰符是 0,public 是 1 ,private 是 2 ,protected 是 4,static 是 8 ,final 是 16】 getReturnType():以 Class 形式获取返回类型 getName:返回方法名 getParameterTypes:以 Class[] 返回参数类型数组(形参)

  • 第四组:java.lang.reflect.Constructor 类 getModifiers:以int形式返回修饰符 getName:返回构造器名(全类名) getParameterTypes:以Class返回参数类型数组

通过反射创建对象

  1. 方式一:调用类中的public修饰的无参构造器

  2. 方式二:调用类中的指定构造器

Class类相关的方法

  • newInstance

  • getConstructor 获得public构造器对象(也可以是有参)

  • getDeclaredConstructor 获得指定的(带参的) 包含了非public

Constructor类相关的方法

  • setAccessible:暴破

  • newInstance(Object obj):调用构造器

public class Test {

    public static void main(String[] args) throws Exception {
        // 获取User类的Class对象
        Class<?> userClass = Class.forName("com.test.User");
        //1.通过public的无参构造器
//        Object o = userClass.newInstance();
//        System.out.println(o);
        Constructor<?> constructor = userClass.getConstructor();
        Object o = constructor.newInstance();
        System.out.println(o);
        //2.通过public的有参构造器
        Constructor<?> constructor1 = userClass.getConstructor(String.class);
        Object wlkk = constructor1.newInstance("wlkk");
        System.out.println(wlkk);
        //3. 通过非public的有参构造器
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        Object wlkkk = declaredConstructor.newInstance("wlkkk", 20);
        System.out.println(wlkkk);
    }
}
class User{
    private int age = 18;
    private String name = "wlk";

    public User(){

    }
    public User(String name){
        this.name = name;
    }
    private User(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

反射操作属性

public class Test {

    public static void main(String[] args) throws Exception {

        //得到User类对应的 Class对象
        Class<?> userClass = Class.forName("com.test.User");
        // 创建对象
        Constructor<?> constructor = userClass.getConstructor();
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class);
        Object user = constructor.newInstance();
        Object user1 = declaredConstructor.newInstance(20);
        System.out.println(user.getClass());
        // 使用反射的到age
        Field age = userClass.getField("age");
        age.set(user,100);
        System.out.println(user);
        //使用反射获取name 使用暴破获得私有
        Field name = userClass.getDeclaredField("name");
        name.setAccessible(true);
//        name.set(user,"wlk");
        name.set(null,"wlk");//因为name为static属性 因此所有的对象公有该属性
        System.out.println(user);
        System.out.println(user1);
    }
}
class User{
   public int age;
   private static String name;

    public User() {
    }

    public User(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "age= " + age +" "+
                "name= "+ name +" "+
                '}';
    }
}

反射访问类中的方法

public class Test {

    public static void main(String[] args) throws Exception {
        //创建User对类对应的Class对象
        Class<?> userClass = Class.forName("com.test.User");
        //创建User类的实例对象
        Object user = userClass.getConstructor().newInstance();
        //得到属性名 并对该user对象的属性名进行赋值
        Field age = userClass.getField("age");
        age.set(user,11);
        Field name = userClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(user,"wlk");
        //创建User类中方法 并唤醒user对应的方法
        //唤醒带参方法
        Method eat = userClass.getMethod("eat",int.class);
        eat.invoke(user,1);
        //唤醒private方法
        Method sayAge = userClass.getDeclaredMethod("sayAge");
        sayAge.setAccessible(true);
        sayAge.invoke(user);
        //唤醒带返回值的方法 统一返回Object
        Method lastCount = userClass.getDeclaredMethod("lastCount", int.class);
        lastCount.setAccessible(true);
        Object last = lastCount.invoke(user,1);
        System.out.println("剩余 "+last+" 餐");

    }
}
class User{
   public int age;
   private String name;

    public User() {
    }

    public User(int age) {
        this.age = age;
    }

    public void eat(int count){
        System.out.println(name+"吃了"+count+" 餐");
    }
    private void sayAge(){
        System.out.println(name + "说自己"+age+"岁");
    }
    protected int lastCount(int count){
        return 3-count;
    }

    @Override
    public String toString() {
        return "User{" +
                "age= " + age +" "+
                "name= "+ name +" "+
                '}';
    }
}

本文章使用limfx的vscode插件快速发布