Java复习--基础篇

Java复习--基础篇

Scroll Down

面向对象

构造方法

名字与类名相同, 没有返回值(其实是有的, 只是不写而已), 构造函数除了通过new时调用, 还可以通过反射方式调用

对象的创建方式

  1. new 关键字
  2. 反射 Class.newInstance / Constructor.newInstance
  3. Clone方法(浅拷贝): 无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数
  4. 反序列化

面向对象特性

  • 继承: Java只能单继承, 但可以多实现, 无法继承构造器, 子类会在构造器中自动调用super()
  • 封装: 严格控制访问
  • 多态: 父类的引用指向子类对象, 这样同一个类型在运行时可能呈现出不一样的特性(不同子类的方法)

静态代码块, 构造代码块, 构造函数

public class HelloWorld {
    static {
        // 静态代码块
        // 类加载时执行, 只执行一次
    }
    {
        // 构造代码块, 每次构造时执行, 先于构造函数
    }
    public HelloWorld() {
    	// code here
	} 
}

final关键字

  • 修饰的变量只能赋值一次
  • 修饰的方法无法被重写
  • 修饰的类无法被继承

抽象类 / 接口

  • 二者都是特殊的, 二者都不能实例化
  • 抽象类除了存在抽象方法其他与普通类相同
  • 抽象类和抽象方法用abstract修饰
  • 接口可以拥有的成员为: 静态常量, 抽象方法, 类(静态)方法, 默认方法, 私有方法(Java9+)
  • 只有一个方法的接口称为函数式接口, 可以使用lambda表达式
  • 接口常用于定义标准, 抽象类常用作实现某个功能的中间件, 体现的是模板设计

内部类

内部类: 把一个类放在另一个类内部即可, 其中的"类内部"可以是类中的任何位置,包括方法中(局部内部类)

静态内部类: 使用static修饰, 该类就属于静态内部类, 就跟静态方法一样, 属于外部类本身, 而不是外部类的对象

非静态内部类: 显然属于外部类对象, 必须先有对象才可能访问

局部内部类: 就是在类方法里定义的, 只在该方法内可以访问

匿名内部类: 创建只需要使用一次的类, 一般会在实现该类的同时创建对象, 由于直接创建实例对象, 没有类名, 所以匿名内部类无法复用

  • 匿名内部类其实是直接new接口或继承某个父类(可以是抽象类), 如果是接口/抽象类必须实现所有方法

Lambda表达式(函数式接口)

Lambda表达式其实就是new的一个接口, 这个接口只有一个函数(函数式接口), 也就是说Lambda表达式其实是匿名内部类的一种简写方式, 不是啥新东西

关于表达式中的方法引用

  • 引用类方法: (a,b,...)->类名.方法名(a,b,...)再简写类名::方法名
  • 引用特定对象的方法, 与类方法一样, 类名换为对象名就行了
  • 引用类的实例方法(a,b,...)->a.方法名(a,b,...)简写a的类名::实例方法名注意, a为调用者, 传参数是传的全部
  • 引用构造器: (a,b,...)->new 类名(a,b,...)简写类名::new

常见类

异常Throwable

Java中异常是一个经常见到的类, 都继承自Throwable

Throwable下又分为ErrorException, Error程序一般无法处理

所以一般提到的异常其实指的是Exception, 是应该捕获的可处理的错误

RuntimeException无需强制捕获,非RuntimeException需强制捕获,或者用throws声明

异常在大多数情况下其实是程序在按正常流程执行时抛出的, 比如认证失败异常, 就是程序正常认证时发现权限不对而抛出的, 我觉得可以理解为这个异常是程序正常运行的一部分

Object

所有类的父类, 包含的方法

  • clone(Object obj): 创建并返回一个对象的拷贝(浅拷贝), 调用该方法必须实现Cloneable接口, 该方法在Object中被protected修饰所以需要子类重写该方法并开放权限
  • equals(Object obj): 判断两个对象是否相等, 默认判断的是引用指向的地址是否相等
  • int hashCode(): 获取对象的hash值, 表示在哈希表中的位置, 如果子类重写了equals方法就需要重写此方法, 该方法为本地方法, 也就是用C/C++实现的, 因为哈希相同对象可能不相等, 但对象相等则哈希一定相同
  • getClass(): 获取对象运行时对象的类
  • wait(): 让当前线程进入等待状态, 直到其他线程调用次对象的notify()notifyAll()方法
  • wait(long timeout): 让当前线程等待, 直到直到其他线程调用次对象的notify()notifyAll()方法, 或超过设置的时间
  • wait(long timeout, int nanos): 与上面的类似, 就是加了纳秒
  • toString(): 返回对象的字符串表示形式

getClass, hashCode, clone, notify, notifyAll, wait(long timeout) 均为本地方法, 由C/C++实现

TheadLocalRandom/Random

Random类专门用于生成一个伪造随机数, 有两个构造器, 默认构造器以时间为种子, 另一个需要显示传入种子数

TheadLocalRandom类是Random类的增强版, 在并发下使用TheadLocalRandom可以减少多线程资源竞争, 因为Random类各线程共享一个实例

新的日期与时间类

  • Clock: 该类用于获取指定时区的当前日期, 时间; 可以取代System类的currentTimeMillis()方法
  • Duration: 该类代表持续时间, 可以方便的获取一段时间
  • Instant: 某个瞬时点
  • LocalDate: ISO-8601日历系统中没有时区的日期, 总是获取当前系统的默认时区
  • LocalDateTime: 日期时间, 与上面一样, 总是获取当前系统的默认时区
  • LocalTime: 时间, 总是获取当前系统的默认时区
  • MothDay: ISO-8601日历系统中的月和日
  • OffsetDateTime: ISO-8601日历系统中与UTC/Greenwich的偏移量的日期时间
  • OffsetTime: ISO-8601日历系统中与UTC/Greenwich的偏移量的时间
  • Period: 时间差
  • ZonedDateTime: 具有时区的日期时间
  • ZoneOffset: 表示格林威治/UTC的时区偏移量

String / StringBuffer / StringBuilder

String: 不可变性, 使用char数组来保存, 且用final修饰, Java9 之后采用 byte数组保存

StringBuffer: 方法都用synchronized修饰, 线程安全

StringBuilder: 线程不安全的

泛型

泛型: 类型参数化, 但这个泛型其实是伪泛型, 因为编译后所有的泛型都会被擦除, 变为Object, 所以Java的泛型只是给编译器看的, JVM根本不知道有泛型这个玩意儿

定义泛型时: extends用于指定类型上限, super用于指定类型下限, ? 用作通配符

枚举

EnumSet

EnumSet 是一种专门为枚举类型所设计的 Set 类型。

HashSet相比,由于使用了内部位向量表示,因此它是特定 Enum 常量集的非常有效且紧凑的表示形式。

它提供了类型安全的替代方法,以替代传统的基于int的“位标志”,使我们能够编写更易读和易于维护的简洁代码。

EnumSet 是抽象类,其有两个实现:RegularEnumSetJumboEnumSet,选择哪一个取决于实例化时枚举中常量的数量。

在很多场景中的枚举常量集合操作(如:取子集、增加、删除、containsAllremoveAll批操作)使用EnumSet非常合适;如果需要迭代所有可能的常量则使用Enum.values()

EnumMap

EnumMap是一个专门化的映射实现,用于将枚举常量用作键。与对应的 HashMap 相比,它是一个高效紧凑的实现,并且在内部表示为一个数组

集合框架

Collection

List: 有序列表

List的行为和数组几乎完全相同, 内部按照插入元素的先后顺序存放, 每个元素都可以通过索引确定自己的位置

  • ArrayList: 内部使用了数组来存储所有元素, 当数组已满了时, 继续添加元素, ArrayList先创建一个更大的新数组,然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组
  • LinkedList: 通过“链表”也实现了List接口, 内部每个元素都指向下一个元素
  • of()方法可以快速创建一个list

Set: 用于存储不重复的元素集合

  • HashSet: 使用HashMap来实现的, 也就是说HashSet就是在操作HashMap的key而已, 不保证有序
  • TreeSet: 跟HashSet差不多, 就是保证插入与取出的顺序相同

Map

Map: K-V映射表, python里叫字典, 我觉得字典好听点

Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。

  • HashMap: 就是根据键的哈希值来存储数据, 也就是说, 平时说的重写hashCode(), 其实如果能保证那个类的对象一定不会是key, 那重不重写也无所谓
  • TreeMap: 跟TreeSet一样, 就是有序了
  • HashTable: 这玩意儿的方法都被synchronized修饰, 所以是线程安全的, 不过基本被淘汰了
  • ConcurrentHashMap: 数据结构跟HashMap一样(数组+链表+红黑树), 这个只会锁当前链表或红黑树的首节点, 提高了性能, 所以现在基本用他来代替HashTable

多线程

简介

进程(Process): 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程

线程(Thread): 进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

线程的状态

  • 创建: new出来就是创建
  • 就绪:
  • 运行:
  • 阻塞:
  • 死亡: destory(), stop(); 两种方式都不推荐使用, 推荐使用一个标志位来控制线程的结束

start()方法, 让进程进入就绪状态

yield()方法, 让当前正在执行的线程暂停, 但不阻塞, 进程将从运行状态变为就绪状态

sleep()方法, 让当前正在执行的线程暂停, 进入阻塞状态, 到达设定时间后在变为就绪状态, 抱着锁睡觉, 不会释放锁的

join()方法, 插队执行, A线程调用B线程的join方法, 这A线程阻塞等待B线程执行完, 但C线程不受影响

getState()方法, 获取线程状态

线程的创建

  • 继承Thread类, 重写run方法, 调用start方法开始线程
  • 实现Runnable接口, 实现run方法, 将Runable实现类对象放入Thread对象中, 再调用start方法
  • 实现Callable接口, 实现call方法, 创建执行服务对象ExecutorService, 使用执行服务对象调用submit方法提交实现的Callable对象, submit会返回一个执行结果对象Future, 可以通过该对象获取执行结果, 最后调用执行服务对象的shutdownNow方法关闭执行服务

线程的优先级

Thread的对象可以设置线程的执行的优先级

  • setPriority(): 设置优先级, 最大为10, 最小为1
  • getPriority(): 获取优先级

守护线程

线程分为用户线程和守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

理解: 当所有的用户线程结束之后, 虚拟机就开始停止了, 这时守护线程可能还在跑, 在虚拟机开始停止到完全停止这段时间守护进程都一直在跑

守护进程用于: 后台记录操作日志, 监控内存, 垃圾回收等

通过调用setDaemon(true)将某个线程设置为守护线程

线程同步

线程并发操作同一个数据, 这时就极可能发生数据错误, 这就是线程不安全性

解决办法: 队列 + 锁

每个线程都在自己的内存空间里操作变量

synchronized

  • 加在方法上方法就变为同步方法, 锁的是this, 并发线程执行同步方法时需要先拿到锁, 也就是同时只能一个线程执行
  • 加在代码块儿({...})前 就变为同步方法块, 一般采用这种发生加锁
  • 锁方法块的时候应该指明锁的对象, synchronized(obj) {...}

注意: 加锁的时候千万不要锁错了,一定要保证锁的对象唯一, 推荐锁共享资源对象

Lock(显式锁)

public class Test implements Runnable {
    
    // 定义锁
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 加锁
        lock.lock();
        try {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        } finally {
            // 解锁
            lock.unlock();
        }
    }
}

线程通信

  • 通过wait()notifyAll()来进行线程间的通信, 这两个方法只能在同步方法/同步代码块儿中使用
  • 使用Lock锁时, 通过Lock对象获取Condition对象, 然后调用Condition对象的await()signalAll()方法
  • Condition可以实现精准通知, 一个Condition对象可以只监视一个线程, 搞多个对象就可以

池化技术

池化技术——线程池、连接池、内存池

池化技术简单点来说,就是提前保存大量的资源,以备不时之需

  • 线程池: 先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态
  • 连接池: 启动时建立足够的数据库连接,并将这些连接组成一个连接池, 由应用程序动态地对池中的连接进行申请、使用和释放
  • 内存池: 在启动的时候,一个内存池(Memory Pool)分配一块很大的内存,并将会将这个大块分成较小的块。每次你从内存池申请内存空间时,它会从先前已经分配的块中得到,而不是从操作系统

线程池:

​ JDK5提供了ExecutorServiceExecutors这个两个关于线程池的API

使用Executors创建ExecutorService对象, 使用ExecutorService对象调用execute()方法传入Runnable对象或调用submit()传入Callable对象