JAVA基础知识复习-Java篇

248 阅读8分钟

集合

List

类型 描述
ArrayList 基于数组,查改快,增删慢(需要移动插入位置之后的数),线程不安全
LinkedList 基于双向列表,增删快(改变指针即可),查询慢(需要遍历),线程不安全
Vector ArrayList的线程安全版,锁操作导致性能低于ArrayList

Set:存储无序且值不同的元素,hashCode相同&&(短路与,遇到false就停止)equals相同视为同一元素

类型 描述
HashSet HashTable实现,无序
TreeSet 二叉树实现,自定义数据类型必须实现Comparable
LinkedHashSet HashTable实现数据存储,双向列表记录顺序

Map

类型 描述
HashMap 数组+链表,线程不安全
HashTable HashMap线程安全版本(类比表锁,颗粒度大)
TreeMap 二叉树实现,自定义数据类型必须实现Comparable
LinkedHashMap HashTable实现数据存储,双向列表记录顺序

HashMap详解

  • HashMap是数组+列表的数据结构。
  • 其中Entry由key、value、hash值、next指针组成
  • HashMap常用参数
    • capacity:当前数组容量,默认16,不够扩2倍
    • loadFactor(负载因子):默认0.75
    • threshold:扩容阀值(触发扩容条件),capacity*loadFactor
  • JDK<1.8:定位到的数组不含链表,一次寻址即可,算法复杂度O(1),含有链表,需要用equals逐一比较,这个过程的算法复杂度O(n)
  • JDK>= 1.8:链表改成红黑树,找到Entry后遍历寻找元素的复杂度从O(n)->O(logn)

为什么线程不安全

  • 哈希碰撞:哈希碰撞时候,线程A判断节点的next指针为null,这时候时间分片用完了,线程B进来了,也判断为null,然后线程A和B都建下一个节点,必然造成其中一个丢成
  • 哈希扩容:当多个线程同时检测到总数量超过阀值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题

为什么HashMap 的长度是2的幂次方

如果长度是2的幂次方,则hash%length = hash&(length-1)成立,与运算代替乘法运算,性能提升

HashTable 和 HashMap 区别

HashMap线程不安全,HashTable线程安全,Hashtable内部方法基本都通过synchronized修饰,保证了可见性。但是Hashtable效率太低,当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态

ConcurrentHashMap

JDK1.7的时候(类似页锁),对Table进行了分割分段,每个Segment(ReentrantLock)锁住一部分Table,默认分配16个Segment,效率也就提升16倍。到了JDK1.8的时候(类似行锁),直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。有几个node提升多少倍

ReetrantLock和synchronized

两者都是可重入锁,即两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。区别的是ReentrantLock提供了创建公平锁的构造函数,ReentrantLock比较灵活,加锁解锁由用户自己控制,一般记得在finally里释放锁

ReetrantReadWriteLock

readLock()获取读锁,writeLock获取写锁,读锁,此时多个线程可以获取读锁,写锁,此时只有一个线程能获得写锁,但读锁和写锁是互斥的,读锁释放后才执行写锁的方法,这个可以类比数据库中的读写锁,(in share mode/for update),这里不在赘述

异常

  • Error:系统内部错误/资源耗尽导致,不能在运行中被动态处理,出现Error程序会自动结束
  • RuntimeException:可以捕获处理
  • CheckedException:编译阶段会检查此类异常,强制要求用户去捕获处理

throw/throws

  • throw:作用在方法内,明确抛出一个异常
  • throws:作用在方法外,定义可能抛出的异常

反射机制

动态获取类和对象的信息,以及动态调用对象的方法

API

  • Class类:用于获取类的属性、方法等信息
  • Field类:表示类的成员变量,用于获取和设置类中的属性值
  • Method类:表示类的方法,用于获取方法的描述信息或者执行某个方法
  • Constructor类:表示类的构造方法

案例

  • 编译时类型和运行时类型
    // 编译时类型为Person,运行时类型为Student,反射机制实现
    Person person = new Student();
    log.info("person的运行时class类型:{}",person.getClass().toString());

  • Class和父Class的获取
    Person person = new Student();
    log.info("获取person的Class方式一:{}",person.getClass().toString());
    log.info("[强制处理异常ClassNotFoundException]获取person的Class方式二:{}",Class.forName("com.example.demo.Student").toString());
    log.info("获取person的父Class方式:{}",person.getClass().getSuperclass().toString());

  • Field的获取以及修改
    Student student = new Student();
    Class aClass = student.getClass();
    Field[] declaredFields = aClass.getDeclaredFields();
    for ( Field field: declaredFields) {
      log.info("字段类型:{}",field.getType());
      log.info("字段名称:{}",field.getName());
    }
    Field field = aClass.getDeclaredField("age");
    boolean accessible = field.isAccessible();
    log.info("age 是否允许访问:{}",accessible);
    field.setAccessible(true);
    log.info("age value修改前:{}",field.get(student));
    field.set(student,2);
    log.info("age value修改后:{}",field.get(student));
    // 还原
    field.setAccessible(accessible);

  • Method的获取以及使用
    Student student = new Student();
    Class aClass = student.getClass();
    Method[] declaredMethods = aClass.getDeclaredMethods();
    log.info("Method:");
    for ( Method method: declaredMethods) {
      log.info("Method Name:{}",method.getName());
    }
    Method method = aClass.getDeclaredMethod("setAge", Integer.class);
    method.invoke(student,2);
    log.info(student.toString());

  • 创建对象的方式
    Class aClass = Class.forName("com.example.demo.Student");
    Student s1 = (Student) aClass.newInstance();
    log.info("存在默认空构造器:{}", s1.toString());
    Constructor c = aClass.getDeclaredConstructor(String.class, Integer.class);
    Student s2 = (Student) c.newInstance("zhao", 18);
    log.info("使用构造器:{}", s2.toString());

注解

标准元注解:负责注解其他注解

  • @Target:注解所修饰的对象范围
    • TYPE:类、接口、枚举
    • FIELD:域
    • METHOD:方法
    • PARAMETER:参数
    • CONSTRUCTOR:构造器
    • LOCAL_VARAIABLE:局部变量
    • ANNOTATION:声明注解
    • PACKAGE:包
    • TYPE_PARAMETER:普通变量
    • TYPE_USE:标注名称
  • @Retention:标注保留级别
    • SOURCE:源文件有效
    • CLASS:Class文件有效
    • RUNTIME:运行时有效
  • @Documented:是否被javadoc工具记录
  • @Inherited:继承(标注同时用于子类)

案例

定义注解(作为对象的元数据,作出一些说明)

@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AirConditioningProvider {
  String address() default "";
}

定义被注解对象

public class Green {
    @AirConditioningProvider(address="珠海市")
    private String name;

    public String getName() {
        return name;
    }

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

定义注解处理器

@Slf4j
public class AirConditioningUtil {
  public static void getName(Class<?> classz) throws Exception {
    Field name = classz.getDeclaredField("name");
    AirConditioningProvider annotation = name.getAnnotation(AirConditioningProvider.class);
    log.info("空调产地:" + annotation.address());
  }

  public static void main(String agrs[]) throws Exception {
    AirConditioningUtil.getName(Green.class);
  }
}

Spring中的常见注解

  • @Resource & @Autowired
    • 相同点
      • 都可以实现注入bean
      • 写在字段/setter处
    • 不同点
      • @Autowired是spring的注解,而@Resource不是,但是spring支持
      • @Autowired是byType(根据类型)注入,默认情况下要求依赖必须存在,配置required=false表示依赖可为null,如果要实现byName(根据名称)需要集合@Qualifier
         // 1
         @Autowired
         @Qualifier("userService")
      
      • @Resource
        • name & type:名称+类型,找不到/不唯一抛出异常
        • name:根据名称,找不到/不唯一抛出异常
        • type:根据类型,找不到/不唯一抛出异常
        • 不指定:byName,找不到则回退为原始类型
         // 等价1
         @Resource("userService")
      
  • @Repository & @Component & @Service & @Controller
    • 相同点
      • 作用相同:注入组件
    • 不同点
      • 意义不同
        • @Component:通用bean
        • @Controller:表现层bean
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Component
        
        • @Service:业务层bean
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Component  
        
        • @Repository:数据访问层bean
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Component
        

内部类

  • 静态内部类(StaticInnerClass):定义在类中的静态类,适用于和外部类关系密切且不依赖外部类实例的类
  • 成员内部类(MemberInnerClass):定义在类中的非静态类
  • 局部内部类(PartInnerClass):定义在类的方法中的非静态类
  • 匿名内部类:通过继承一个父类或者实现一个接口直接定义且使用
  • 一句话总结:静态内部类只能访问外部类的静态变量和静态方法,成员内部类不能定义静态方法和静态变量(final修饰除外),局部内部类成员内部类要求一致

泛型

泛型即参数化类型,编译时自动转为具体类型(类型擦除),编译期就能够判断类型是否安全,同时类型转化是自动、隐式的,提高了代码的安全性和重用性

类型 描述
E-Element 集合中使用,表示在集合中存放的元素
T-Type JAVA类
K-Key
V-Value
N-Number 数值类型
不确定的类型
  • 上限:<? extends T>,?是T的子类
  • 下限:<? super T>,?是T的父类

案例

// 泛型接口
public interface IGeneral<T> {
    T id();
}
// 泛型类
public class GeneralClass<T> implements IGeneral<Long> {
  private T t;

  public T getT() {
    return t;
  }

  public void setT(T t) {
    this.t = t;
  }

  @Override
  public Long id() {
    return 1314L;
  }
  // 泛型方法
  <T> void generalMethod(T... array) {
    for (T t : array) {
      if (t instanceof Long) {
        log.info("Long");
      } else {
        log.info("Other");
      }
    }
  }

  public static void main(String args[]) {
    GeneralClass<Long> longGeneralClass = new GeneralClass<>();
    longGeneralClass.generalMethod(longGeneralClass.id());
  }
}

序列化

保存对象及其状态以便下次使用

要点

  • 实现Serializable接口
  • 对象ID保持一致(private static final long serialVersionUID)
  • 序列化不保存静态变量
  • 序列化父类变量时候需要父类实现Serializable接口
  • Transient修饰可以阻止序列化,反序列化后变成该类型初始值

案例

@Data
@Builder
public class Student implements Serializable {
  // 序列化UID
  private static final long serialVersionUID = 1L;
  private String name;
  // 不会被序列化
  private transient Integer age;
  // 不会被序列化
  private static Integer sex = 1;

  public static void main(String args[]) throws Exception {
    FileOutputStream fileOutputStream = new FileOutputStream("Student.out");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(Student.builder().name("zhao").age(18).build());
    objectOutputStream.flush();
    objectOutputStream.close();
    FileInputStream fileInputStream = new FileInputStream("Student.out");
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    Student student = (Student) objectInputStream.readObject();
    System.out.println(student.toString());
  }
}