我肝了一个月,给你写出了这本Java开发手册。(五)

简介: 先来看一下本篇文章的思维导图吧,我会围绕下面这些内容进行讲解。内容很干,小伙伴们看完还希望不吝转发。(高清思维导图版本关注作者公众号 Java建设者 回复 Java666 获取,其他思维导图获取方式在文末)。

接口和抽象类

接口

接口相当于就是对外的一种约定和标准,这里拿操作系统举例子,为什么会有操作系统?就会为了屏蔽软件的复杂性和硬件的简单性之间的差异,为软件提供统一的标准。

在 Java 语言中,接口是由 interface 关键字来表示的,比如我们可以向下面这样定义一个接口

public interface CxuanGoodJob {}

比如我们定义了一个 CxuanGoodJob 的接口,然后你就可以在其内部定义 cxuan 做的好的那些事情,比如 cxuan 写的文章不错。

public interface CxuanGoodJob {
    void writeWell();
}

这里隐含了一些接口的特征:

  • interface 接口是一个完全抽象的类,他不会提供任何方法的实现,只是会进行方法的定义。
  • 接口中只能使用两种访问修饰符,一种是 public,它对整个项目可见;一种是 default 缺省值,它只具有包访问权限。
  • 接口只提供方法的定义,接口没有实现,但是接口可以被其他类实现。也就是说,实现接口的类需要提供方法的实现,实现接口使用 implements 关键字来表示,一个接口可以有多个实现。
class CXuanWriteWell implements CxuanGoodJob{
    @Override
    public void writeWell() {
        System.out.println("Cxuan write Java is vary well");
    }
}
  • 接口不能被实例化,所以接口中不能有任何构造方法,你定义构造方法编译会出错。
  • 接口的实现比如实现接口的全部方法,否则必须定义为抽象类,这就是我们下面要说的内容

抽象类

抽象类是一种抽象能力弱于接口的类,在 Java 中,抽象类使用 abstract 关键字来表示。如果把接口形容为狗这个物种,那么抽象类可以说是毛发是白色、小体的品种,而实现类可以是具体的类,比如说是博美、泰迪等。你可以像下面这样定义抽象类

public interface Dog {
    void FurColor();
}
abstract class WhiteDog implements Dog{
    public void FurColor(){
        System.out.println("Fur is white");
    }
    abstract void SmallBody();
}

在抽象类中,具有如下特征

  • 如果一个类中有抽象方法,那么这个类一定是抽象类,也就是说,使用关键字 abstract 修饰的方法一定是抽象方法,具有抽象方法的类一定是抽象类。实现类方法中只有方法具体的实现。
  • 抽象类中不一定只有抽象方法,抽象类中也可以有具体的方法,你可以自己去选择是否实现这些方法。
  • 抽象类中的约束不像接口那么严格,你可以在抽象类中定义 构造方法、抽象方法、普通属性、方法、静态属性和静态方法
  • 抽象类和接口一样不能被实例化,实例化只能实例化具体的类

异常

异常是程序经常会出现的,发现错误的最佳时机是在编译阶段,也就是你试图在运行程序之前。但是,在编译期间并不能找到所有的错误,有一些 NullPointerExceptionClassNotFoundException 异常在编译期找不到,这些异常是 RuntimeException 运行时异常,这些异常往往在运行时才能被发现。

我们写 Java 程序经常会出现两种问题,一种是 java.lang.Exception ,一种是 java.lang.Error,都用来表示出现了异常情况,下面就针对这两种概念进行理解。

认识 Exception

Exception 位于 java.lang 包下,它是一种顶级接口,继承于 Throwable 类,Exception 类及其子类都是 Throwable 的组成条件,是程序出现的合理情况。

在认识 Exception 之前,有必要先了解一下什么是 Throwable

什么是 Throwable

Throwable 类是 Java 语言中所有错误(errors)异常(exceptions)的父类。只有继承于 Throwable 的类或者其子类才能够被抛出,还有一种方式是带有 Java 中的 @throw 注解的类也可以抛出。

在Java规范中,对非受查异常和受查异常的定义是这样的:

The unchecked exception classes are the run-time exception classes and the error classes.

The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable and all its subclasses other than RuntimeException and its subclasses and Errorand its subclasses.

也就是说,除了 RuntimeException 和其子类,以及error和其子类,其它的所有异常都是 checkedException

那么,按照这种逻辑关系,我们可以对 Throwable 及其子类进行归类分析

微信图片_20220414190108.png

可以看到,Throwable 位于异常和错误的最顶层,我们查看 Throwable 类中发现它的方法和属性有很多,我们只讨论其中几个比较常用的

// 返回抛出异常的详细信息
public string getMessage();
public string getLocalizedMessage();
//返回异常发生时的简要描述
public public String toString();
// 打印异常信息到标准输出流上
public void printStackTrace();
public void printStackTrace(PrintStream s);
public void printStackTrace(PrintWriter s)
// 记录栈帧的的当前状态
public synchronized Throwable fillInStackTrace();

此外,因为 Throwable 的父类也是 Object,所以常用的方法还有继承其父类的getClass()getName() 方法。

常见的 Exception

下面我们回到 Exception 的探讨上来,现在你知道了 Exception 的父类是 Throwable,并且 Exception 有两种异常,一种是 RuntimeException ;一种是 CheckedException,这两种异常都应该去捕获

下面列出了一些 Java 中常见的异常及其分类,这块面试官也可能让你举出几个常见的异常情况并将其分类

RuntimeException

微信图片_20220414190113.png

UncheckedException

微信图片_20220414190118.png

与 Exception 有关的 Java 关键字

那么 Java 中是如何处理这些异常的呢?在 Java 中有这几个关键字 throws、throw、try、finally、catch 下面我们分别来探讨一下

throws 和 throw

在 Java 中,异常也就是一个对象,它能够被程序员自定义抛出或者应用程序抛出,必须借助于 throwsthrow 语句来定义抛出异常。

throws 和 throw 通常是成对出现的,例如

static void cacheException() throws Exception{
  throw new Exception();
}

throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。throws 语句用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。

throws 主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。throw 是具体向外抛异常的动作,所以它是抛出一个异常实例。

try 、finally 、catch

这三个关键字主要有下面几种组合方式 try...catch 、try...finally、try...catch...finally

try...catch 表示对某一段代码可能抛出异常进行的捕获,如下

static void cacheException() throws Exception{
  try {
    System.out.println("1");
  }catch (Exception e){
    e.printStackTrace();
  }
}

try...finally 表示对一段代码不管执行情况如何,都会走 finally 中的代码

static void cacheException() throws Exception{
  for (int i = 0; i < 5; i++) {
    System.out.println("enter: i=" + i);
    try {
      System.out.println("execute: i=" + i);
      continue;
    } finally {
      System.out.println("leave: i=" + i);
    }
  }
}

try...catch...finally 也是一样的,表示对异常捕获后,再走 finally 中的代码逻辑。

什么是 Error

Error 是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。这些错误是不可检查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况,比如 OutOfMemoryErrorStackOverflowError异常的出现会有几种情况,这里需要先介绍一下 Java 内存模型 JDK1.7。

微信图片_20220414190123.png

其中包括两部分,由所有线程共享的数据区和线程隔离的数据区组成,在上面的 Java 内存模型中,只有程序计数器是不会发生 OutOfMemoryError 情况的区域,程序计数器控制着计算机指令的分支、循环、跳转、异常处理和线程恢复,并且程序计数器是每个线程私有的。

什么是线程私有:表示的就是各条线程之间互不影响,独立存储的内存区域。

如果应用程序执行的是 Java 方法,那么这个计数器记录的就是虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)

除了程序计数器外,其他区域:方法区(Method Area)虚拟机栈(VM Stack)本地方法栈(Native Method Stack)堆(Heap) 都是可能发生 OutOfMemoryError 的区域。

  • 虚拟机栈:如果线程请求的栈深度大于虚拟机栈所允许的深度,将会出现 StackOverflowError 异常;如果虚拟机动态扩展无法申请到足够的内存,将出现 OutOfMemoryError
  • 本地方法栈和虚拟机栈一样
  • 堆:Java 堆可以处于物理上不连续,逻辑上连续,就像我们的磁盘空间一样,如果堆中没有内存完成实例分配,并且堆无法扩展时,将会抛出 OutOfMemoryError。
  • 方法区:方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。

在 Java 中,你可以把异常理解为是一种能够提高你程序健壮性的机制,它能够让你在编写代码中注意这些问题,也可以说,如果你写代码不会注意这些异常情况,你是无法成为一位硬核程序员的。

内部类

距今为止,我们了解的都是普通类的定义,那就是直接在 IDEA 中直接新建一个 class 。

微信图片_20220414190128.png

新建完成后,你就会拥有一个 class 文件的定义,这种操作太简单了,时间长了就会枯燥,我们年轻人多需要更新潮和骚气的写法,好吧,既然你提到了那就使用 内部类吧,这是一种有用而且骚气的定义类的方式,内部类的定义非常简单:可以将一个类的定义放在另一个类的内部,这就是内部类

内部类是一种非常有用的特性,定义在类内部的类,持有外部类的引用,但却对其他外部类不可见,看起来就像是一种隐藏代码的机制,就和 弗兰奇将军 似的,弗兰奇可以和弗兰奇将军进行通讯,但是外面的敌人却无法直接攻击到弗兰奇本体。

微信图片_20220414190132.png

下面我们就来聊一聊创建内部类的方式。

创建内部类

定义内部类非常简单,就是直接将一个类定义在外围类的里面,如下代码所示

public class OuterClass {
    private String name ;
    private int age;
    class InnerClass{
        public InnerClass(){
            name = "cxuan";
            age = 25;
        }
    }
}

在这段代码中,InnerClass 就是 OuterClass 的一个内部类。也就是说,每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。这也是隐藏了内部实现细节。内部类拥有外部类的访问权

内部类不仅仅能够定义在类的内部,还可以定义在方法和作用域内部,这种被称为局部内部类,除此之外,还有匿名内部类、内部类可以实现 Java 中的 多重继承。下面是定义内部类的方式

一个在方法中定义的类(局部内部类)
一个定义在作用域内的类,这个作用域在方法的内部(成员内部类)
一个实现了接口的匿名类(匿名内部类)
一个匿名类,它扩展了非默认构造器的类
一个匿名类,执行字段初始化操作
一个匿名类,它通过实例初始化实现构造

由于每个类都会产生一个 .class 文件,其中包含了如何创建该类型的对象的全部信息,那么,如何表示内部类的信息呢?可以使用 $ 来表示,比如 OuterClass$InnerClass.class

集合

集合在我们的日常开发中所使用的次数简直太多了,你已经把它们都用的熟透了,但是作为一名合格的程序员,你不仅要了解它的基本用法,你还要了解它的源码;存在即合理,你还要了解它是如何设计和实现的,你还要了解它的衍生过程。

这篇博客就来详细介绍一下 Collection 这个庞大集合框架的家族体系和成员,让你了解它的设计与实现。

是时候祭出这张神图了

微信图片_20220414190138.png

首先来介绍的就是列表爷爷辈儿的接口- Iterator

Iterable 接口

实现此接口允许对象成为 for-each 循环的目标,也就是增强 for 循环,它是 Java 中的一种语法糖

List<Object> list = new ArrayList();
for (Object obj: list){}

除了实现此接口的对象外,数组也可以用 for-each 循环遍历,如下:

Object[] list = new Object[10];
for (Object obj: list){}

其他遍历方式

jdk 1.8之前Iterator只有 iterator 一个方法,就是

Iterator<T> iterator();

实现次接口的方法能够创建一个轻量级的迭代器,用于安全的遍历元素,移除元素,添加元素。这里面涉及到一个 fail-fast 机制。

总之一点就是能创建迭代器进行元素的添加和删除的话,就尽量使用迭代器进行添加和删除。

也可以使用迭代器的方式进行遍历

for(Iterator it = coll.iterator(); it.hasNext(); ){
    System.out.println(it.next());
}

顶层接口

Collection 是一个顶层接口,它主要用来定义集合的约定

List 接口也是一个顶层接口,它继承了 Collection 接口 ,同时也是 ArrayList、LinkedList 等集合元素的父类

Set 接口位于与 List 接口同级的层次上,它同时也继承了 Collection 接口。Set 接口提供了额外的规定。它对add、equals、hashCode  方法提供了额外的标准。

Queue 是和 List、Set 接口并列的 Collection 的三大接口之一。Queue 的设计用来在处理之前保持元素的访问次序。除了 Collection 基础的操作之外,队列提供了额外的插入,读取,检查操作。

SortedSet 接口直接继承于 Set 接口,使用 Comparable 对元素进行自然排序或者使用 Comparator 在创建时对元素提供定制的排序规则。set 的迭代器将按升序元素顺序遍历集合。

Map 是一个支持 key-value 存储的对象,Map 不能包含重复的 key,每个键最多映射一个值。这个接口代替了Dictionary 类,Dictionary 是一个抽象类而不是接口。

ArrayList

ArrayList 是实现了 List 接口的可扩容数组(动态数组),它的内部是基于数组实现的。它的具体定义如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...}
  • ArrayList 可以实现所有可选择的列表操作,允许所有的元素,包括空值。ArrayList 还提供了内部存储 list 的方法,它能够完全替代 Vector,只有一点例外,ArrayList 不是线程安全的容器。
  • ArrayList 有一个容量的概念,这个数组的容量就是 List 用来存储元素的容量。
  • ArrayList 不是线程安全的容器,如果多个线程中至少有两个线程修改了 ArrayList 的结构的话就会导致线程安全问题,作为替代条件可以使用线程安全的 List,应使用 Collections.synchronizedList
List list = Collections.synchronizedList(new ArrayList(...))
  • ArrayList 具有 fail-fast 快速失败机制,能够对 ArrayList 作出失败检测。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生 fail-fast,即抛出 ConcurrentModificationException异常。

Vector

Vector 同 ArrayList 一样,都是基于数组实现的,只不过 Vector 是一个线程安全的容器,它对内部的每个方法都简单粗暴的上锁,避免多线程引起的安全性问题,但是通常这种同步方式需要的开销比较大,因此,访问元素的效率要远远低于 ArrayList。

还有一点在于扩容上,ArrayList 扩容后的数组长度会增加 50%,而 Vector 的扩容长度后数组会增加一倍。

LinkedList 类

LinkedList 是一个双向链表,允许存储任何元素(包括 null )。它的主要特性如下:

  • LinkedList 所有的操作都可以表现为双向性的,索引到链表的操作将遍历从头到尾,视哪个距离近为遍历顺序。
  • 注意这个实现也不是线程安全的,如果多个线程并发访问链表,并且至少其中的一个线程修改了链表的结构,那么这个链表必须进行外部加锁。或者使用
List list = Collections.synchronizedList(new LinkedList(...))

Stack

堆栈是我们常说的后入先出(吃了吐)的容器 。它继承了 Vector 类,提供了通常用的 push 和 pop 操作,以及在栈顶的 peek 方法,测试 stack 是否为空的 empty 方法,和一个寻找与栈顶距离的 search 方法。

第一次创建栈,不包含任何元素。一个更完善,可靠性更强的 LIFO 栈操作由 Deque 接口和他的实现提供,应该优先使用这个类

Deque<Integer> stack = new ArrayDeque<Integer>()

HashSet

HashSet 是 Set 接口的实现类,由哈希表支持(实际上 HashSet 是 HashMap 的一个实例)。它不能保证集合的迭代顺序。这个类允许 null 元素。

  • 注意这个实现不是线程安全的。如果多线程并发访问 HashSet,并且至少一个线程修改了set,必须进行外部加锁。或者使用 Collections.synchronizedSet() 方法重写。
  • 这个实现支持 fail-fast 机制。

TreeSet

TreeSet 是一个基于 TreeMap 的 NavigableSet 实现。这些元素使用他们的自然排序或者在创建时提供的Comparator 进行排序,具体取决于使用的构造函数。

  • 此实现为基本操作 add,remove 和 contains 提供了 log(n) 的时间成本。
  • 注意这个实现不是线程安全的。如果多线程并发访问 TreeSet,并且至少一个线程修改了 set,必须进行外部加锁。或者使用
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...))
  • 这个实现持有 fail-fast 机制。

LinkedHashSet 类

LinkedHashSet 继承于 Set,先来看一下 LinkedHashSet 的继承体系:

微信图片_20220414190146.png

LinkedHashSet 是 Set 接口的 Hash 表和 LinkedList 的实现。这个实现不同于 HashSet 的是它维护着一个贯穿所有条目的双向链表。此链表定义了元素插入集合的顺序。注意:如果元素重新插入,则插入顺序不会受到影响。

  • LinkedHashSet 有两个影响其构成的参数:初始容量和加载因子。它们的定义与 HashSet 完全相同。但请注意:对于 LinkedHashSet,选择过高的初始容量值的开销要比 HashSet 小,因为 LinkedHashSet 的迭代次数不受容量影响。
  • 注意 LinkedHashSet 也不是线程安全的,如果多线程同时访问 LinkedHashSet,必须加锁,或者通过使用
Collections.synchronizedSet
  • 该类也支持fail-fast机制

PriorityQueue

PriorityQueue 是 AbstractQueue 的实现类,优先级队列的元素根据自然排序或者通过在构造函数时期提供Comparator 来排序,具体根据构造器判断。PriorityQueue 不允许 null 元素。

  • 队列的头在某种意义上是指定顺序的最后一个元素。队列查找操作 poll,remove,peek 和 element 访问队列头部元素。
  • 优先级队列是无限制的,但具有内部 capacity,用于控制用于在队列中存储元素的数组大小。
  • 该类以及迭代器实现了 Collection、Iterator 接口的所有可选方法。这个迭代器提供了 iterator() 方法不能保证以任何特定顺序遍历优先级队列的元素。如果你需要有序遍历,考虑使用 Arrays.sort(pq.toArray())
  • 注意这个实现不是线程安全的,多线程不应该并发访问 PriorityQueue 实例如果有某个线程修改了队列的话,使用线程安全的类 PriorityBlockingQueue

HashMap

HashMap 是一个利用哈希表原理来存储元素的集合,并且允许空的 key-value 键值对。HashMap 是非线程安全的,也就是说在多线程的环境下,可能会存在问题,而 Hashtable 是线程安全的容器。HashMap 也支持 fail-fast 机制。HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。可以使用 Collections.synchronizedMap(new HashMap(...)) 来构造一个线程安全的 HashMap。

TreeMap 类

一个基于 NavigableMap 实现的红黑树。这个 map 根据 key 自然排序存储,或者通过 Comparator 进行定制排序。

  • TreeMap 为 containsKey,get,put 和remove方法提供了 log(n) 的时间开销。
  • 注意这个实现不是线程安全的。如果多线程并发访问 TreeMap,并且至少一个线程修改了 map,必须进行外部加锁。这通常通过在自然封装集合的某个对象上进行同步来实现,或者使用 SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...))
  • 这个实现持有fail-fast机制。

LinkedHashMap 类

LinkedHashMap 是 Map 接口的哈希表和链表的实现。这个实现与 HashMap 不同之处在于它维护了一个贯穿其所有条目的双向链表。这个链表定义了遍历顺序,通常是插入 map 中的顺序。

  • 它提供一个特殊的 LinkedHashMap(int,float,boolean) 构造器来创建 LinkedHashMap,其遍历顺序是其最后一次访问的顺序。
  • 可以重写 removeEldestEntry(Map.Entry) 方法,以便在将新映射添加到 map 时强制删除过期映射的策略。
  • 这个类提供了所有可选择的 map 操作,并且允许 null 元素。由于维护链表的额外开销,性能可能会低于HashMap,有一条除外:遍历 LinkedHashMap 中的 collection-views 需要与 map.size 成正比,无论其容量如何。HashMap 的迭代看起来开销更大,因为还要求时间与其容量成正比。
  • LinkedHashMap 有两个因素影响了它的构成:初始容量和加载因子。
  • 注意这个实现不是线程安全的。如果多线程并发访问LinkedHashMap,并且至少一个线程修改了map,必须进行外部加锁。这通常通过在自然封装集合的某个对象上进行同步来实现 Map m = Collections.synchronizedMap(new LinkedHashMap(...))
  • 这个实现持有fail-fast机制。

Hashtable 类

Hashtable 类实现了一个哈希表,能够将键映射到值。任何非空对象都可以用作键或值。

  • 此实现类支持 fail-fast 机制
  • 与新的集合实现不同,Hashtable 是线程安全的。如果不需要线程安全的容器,推荐使用 HashMap,如果需要多线程高并发,推荐使用 ConcurrentHashMap

IdentityHashMap 类

IdentityHashMap 是比较小众的 Map 实现了。

  • 这个类不是一个通用的 Map 实现!虽然这个类实现了 Map 接口,但它故意违反了 Map 的约定,该约定要求在比较对象时使用 equals 方法,此类仅适用于需要引用相等语义的极少数情况。
  • 同 HashMap,IdentityHashMap 也是无序的,并且该类不是线程安全的,如果要使之线程安全,可以调用Collections.synchronizedMap(new IdentityHashMap(...))方法来实现。
  • 支持fail-fast机制

WeakHashMap 类

WeakHashMap 类基于哈希表的 Map 基础实现,带有弱键。WeakHashMap 中的 entry 当不再使用时还会自动移除。更准确的说,给定key的映射的存在将不会阻止 key 被垃圾收集器丢弃。

  • 基于 map 接口,是一种弱键相连,WeakHashMap 里面的键会自动回收
  • 支持 null 值和 null 键。
  • fast-fail 机制
  • 不允许重复
  • WeakHashMap 经常用作缓存

Collections 类

Collections 不属于 Java 框架继承树上的内容,它属于单独的分支,Collections 是一个包装类,它的作用就是为集合框架提供某些功能实现,此类只包括静态方法操作或者返回 collections。

同步包装

同步包装器将自动同步(线程安全性)添加到任意集合。六个核心集合接口(Collection,Set,List,Map,SortedSet 和 SortedMap)中的每一个都有一个静态工厂方法。

public static  Collection synchronizedCollection(Collection c);
public static  Set synchronizedSet(Set s);
public static  List synchronizedList(List list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static  SortedSet synchronizedSortedSet(SortedSet s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);

不可修改的包装

不可修改的包装器通过拦截修改集合的操作并抛出 UnsupportedOperationException,主要用在下面两个情景:

  • 构建集合后使其不可变。在这种情况下,最好不要去获取返回 collection 的引用,这样有利于保证不变性
  • 允许某些客户端以只读方式访问你的数据结构。你保留对返回的 collection 的引用,但分发对包装器的引用。通过这种方式,客户可以查看但不能修改,同时保持完全访问权限。

这些方法是:

public static  Collection unmodifiableCollection(Collection<? extends T> c);
public static  Set unmodifiableSet(Set<? extends T> s);
public static  List unmodifiableList(List<? extends T> list);
public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);
public static  SortedSet unmodifiableSortedSet(SortedSet<? extends T> s);
public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m);

线程安全的Collections

Java1.5 并发包 (java.util.concurrent) 提供了线程安全的 collections 允许遍历的时候进行修改,通过设计iterator 为 fail-fast 并抛出 ConcurrentModificationException。一些实现类是CopyOnWriteArrayListConcurrentHashMapCopyOnWriteArraySet

Collections 算法

此类包含用于集合框架算法的方法,例如二进制搜索,排序,重排,反向等。

集合实现类特征图

下图汇总了部分集合框架的主要实现类的特征图,让你能有清晰明了看出每个实现类之间的差异性

微信图片_20220414190154.png

还有一种类型是关于强引用、弱引用、虚引用的文章,请参考

https://mp.weixin.qq.com/s/ZflBpn2TBzTNv_-G-zZxNg

泛形

在 Jdk1.5 中,提出了一种新的概念,那就是泛型,那么什么是泛型呢?

泛型其实就是一种参数化的集合,它限制了你添加进集合的类型。泛型的本质就是一种参数化类型。多态也可以看作是泛型的机制。一个类继承了父类,那么就能通过它的父类找到对应的子类,但是不能通过其他类来找到具体要找的这个类。泛型的设计之处就是希望对象或方法具有最广泛的表达能力。

下面来看一个例子说明没有泛型的用法

List arrayList = new ArrayList();
arrayList.add("cxuan");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
  System.out.println("test === ", item);
}

这段程序不能正常运行,原因是 Integer  类型不能直接强制转换为 String 类型

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

如果我们用泛型进行改写后,示例代码如下

List<String> arrayList = new ArrayList<String>();
arrayList.add(100);

这段代码在编译期间就会报错,编译器会在编译阶段就能够帮我们发现类似这样的问题。

泛型的使用

泛型的使用有多种方式,下面我们就来一起探讨一下。

用泛型表示类

泛型可以加到类上面,来表示这个类的类型

//此处 T 可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class GenericDemo<T>{ 
    //value 这个成员变量的类型为T,T的类型由外部指定  
    private T value;
    public GenericDemo(T value) {
        this.value = value;
    }
    public T getValue(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return value;
    }
   public void setValue(T value){
       this.value = value
    }
}

用泛型表示接口

泛型接口与泛型类的定义及使用基本相同。

//定义一个泛型接口
public interface Generator<T> {
    public T next();
}

一般泛型接口常用于 生成器(generator) 中,生成器相当于对象工厂,是一种专门用来创建对象的类。

泛型方法

可以使用泛型来表示方法

public class GenericMethods {
  public <T> void f(T x){
    System.out.println(x.getClass().getName());
  }
}

泛型通配符

List 是泛型类,为了 表示各种泛型 List 的父类,可以使用类型通配符,类型通配符使用问号(?)表示,它的元素类型可以匹配任何类型。例如

public static void main(String[] args) {
    List<String> name = new ArrayList<String>();
    List<Integer> age = new ArrayList<Integer>();
    List<Number> number = new ArrayList<Number>();
    name.add("cxuan");
    age.add(18);
    number.add(314);
    generic(name);
    generic(age);
    generic(number);   
}
public static void generic(List<?> data) {
    System.out.println("Test cxuan :" + data.get(0));
}

上界通配符 :  <? extends ClassType> 该通配符为 ClassType 的所有子类型。它表示的是任何类型都是 ClassType 类型的子类。

下界通配符:<? super ClassType> 该通配符为 ClassType 的所有超类型。它表示的是任何类型的父类都是 ClassType。

反射

反射是 Java 中一个非常重要同时也是一个高级特性,基本上 Spring 等一系列框架都是基于反射的思想写成的。我们首先来认识一下什么反射。

Java 反射机制是在程序的运行过程中,对于任何一个类,都能够知道它的所有属性和方法;对于任意一个对象,都能够知道调用它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。(来源于百度百科)

Java 反射机制主要提供了以下这几个功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所有的成员变量和方法
  • 在运行时调用任意一个对象的方法

这么一看,反射就像是一个掌控全局的角色,不管你程序怎么运行,我都能够知道你这个类有哪些属性和方法,你这个对象是由谁调用的,嗯,很屌。

在 Java 中,使用 Java.lang.reflect包实现了反射机制。Java.lang.reflect 所设计的类如下

微信图片_20220414190200.png

下面是一个简单的反射类

public class Person {
    public String name;// 姓名
    public int age;// 年龄
    public Person() {
        super();
    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String showInfo() {
        return "name=" + name + ", age=" + age;
    }
}
public class Student extends Person implements Study {
    public String className;// 班级
    private String address;// 住址
    public Student() {
        super();
    }
    public Student(String name, int age, String className, String address) {
        super(name, age);
        this.className = className;
        this.address = address;
    }
    public Student(String className) {
        this.className = className;
    }
    public String toString() {
        return "姓名:" + name + ",年龄:" + age + ",班级:" + className + ",住址:"
                + address;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}
public class TestRelect {
    public static void main(String[] args) {
        Class student = null;
        try {
            student = Class.forName("com.cxuan.reflection.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 获取对象的所有公有属性。
        Field[] fields = student.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
        System.out.println("---------------------");
        // 获取对象所有属性,但不包含继承的。
        Field[] declaredFields = student.getDeclaredFields();
        for (Field df : declaredFields) {
            System.out.println(df);
        }
       // 获取对象的所有公共方法
        Method[] methods = student.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
        System.out.println("---------------------");
        // 获取对象所有方法,但不包含继承的
        Method[] declaredMethods = student.getDeclaredMethods();
        for (Method dm : declaredMethods) {
            System.out.println(dm);
        }
       // 获取对象所有的公共构造方法
        Constructor[] constructors = student.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }
        System.out.println("---------------------");
        // 获取对象所有的构造方法
        Constructor[] declaredConstructors = student.getDeclaredConstructors();
        for (Constructor dc : declaredConstructors) {
            System.out.println(dc);
        }
       Class c = Class.forName("com.cxuan.reflection.Student");
       Student stu1 = (Student) c.newInstance();
       // 第一种方法,实例化默认构造方法,调用set赋值
        stu1.setAddress("河北石家庄");
        System.out.println(stu1);
        // 第二种方法 取得全部的构造函数 使用构造函数赋值
        Constructor<Student> constructor = c.getConstructor(String.class, 
                                                            int.class, String.class, String.class);
        Student student2 = (Student) constructor.newInstance("cxuan", 24, "六班", "石家庄");
        System.out.println(student2);
        /**
        * 獲取方法并执行方法
        */
        Method show = c.getMethod("showInfo");//获取showInfo()方法
        Object object = show.invoke(stu2);//调用showInfo()方法
    }
}

有一些是比较常用的,有一些是我至今都没见过怎么用的,下面进行一个归类。

与 Java 反射有关的类主要有

Class 类

在 Java 中,你每定义一个 java class 实体都会产生一个 Class 对象。也就是说,当我们编写一个类,编译完成后,在生成的 .class 文件中,就会产生一个 Class 对象,这个 Class 对象用于表示这个类的类型信息。Class 中没有公共的构造器,也就是说 Class 对象不能被实例化。下面来简单看一下 Class 类都包括了哪些方法

toString()

public String toString() {
  return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
    + getName();
}

toString() 方法能够将对象转换为字符串,toString() 首先会判断 Class 类型是否是接口类型,也就是说,普通类和接口都能够用 Class 对象来表示,然后再判断是否是基本数据类型,这里判断的都是基本数据类型和包装类,还有 void类型。

所有的类型如下

  • java.lang.Boolean : 代表 boolean 数据类型的包装类
  • java.lang.Character: 代表 char 数据类型的包装类
  • java.lang.Byte: 代表 byte 数据类型的包装类
  • java.lang.Short: 代表 short 数据类型的包装类
  • java.lang.Integer: 代表 int 数据类型的包装类
  • java.lang.Long: 代表 long 数据类型的包装类
  • java.lang.Float: 代表 float 数据类型的包装类
  • java.lang.Double: 代表 double 数据类型的包装类
  • java.lang.Void: 代表 void 数据类型的包装类

然后是 getName() 方法,这个方法返回类的全限定名称。

  • 如果是引用类型,比如 String.class.getName()  -> java.lang.String
  • 如果是基本数据类型,byte.class.getName() -> byte
  • 如果是数组类型,new Object[3]).getClass().getName() -> [Ljava.lang.Object

toGenericString()

这个方法会返回类的全限定名称,而且包括类的修饰符和类型参数信息。

forName()

根据类名获得一个 Class 对象的引用,这个方法会使类对象进行初始化。

例如 Class t = Class.forName("java.lang.Thread") 就能够初始化一个 Thread 线程对象

在 Java 中,一共有三种获取类实例的方式

  • Class.forName(java.lang.Thread)
  • Thread.class
  • thread.getClass()

newInstance()

创建一个类的实例,代表着这个类的对象。上面 forName() 方法对类进行初始化,newInstance 方法对类进行实例化。

getClassLoader()

获取类加载器对象。

getTypeParameters()

按照声明的顺序获取对象的参数类型信息。

getPackage()

返回类的包

getInterfaces()

获得当前类实现的类或是接口,可能是有多个,所以返回的是 Class 数组。

Cast

把对象转换成代表类或是接口的对象

asSubclass(Class clazz)

把传递的类的对象转换成代表其子类的对象

getClasses()

返回一个数组,数组中包含该类中所有公共类和接口类的对象

getDeclaredClasses()

返回一个数组,数组中包含该类中所有类和接口类的对象

getSimpleName()

获得类的名字

getFields()

获得所有公有的属性对象

getField(String name)

获得某个公有的属性对象

getDeclaredField(String name)

获得某个属性对象

getDeclaredFields()

获得所有属性对象

getAnnotation(Class annotationClass)

返回该类中与参数类型匹配的公有注解对象

getAnnotations()

返回该类所有的公有注解对象

getDeclaredAnnotation(Class annotationClass)

返回该类中与参数类型匹配的所有注解对象

getDeclaredAnnotations()

返回该类所有的注解对象

getConstructor(Class...<?> parameterTypes)

获得该类中与参数类型匹配的公有构造方法

getConstructors()

获得该类的所有公有构造方法

getDeclaredConstructor(Class...<?> parameterTypes)

获得该类中与参数类型匹配的构造方法

getDeclaredConstructors()

获得该类所有构造方法

getMethod(String name, Class...<?> parameterTypes)

获得该类某个公有的方法

getMethods()

获得该类所有公有的方法

getDeclaredMethod(String name, Class...<?> parameterTypes)

获得该类某个方法

getDeclaredMethods()

获得该类所有方法

Field 类

Field 类提供类或接口中单独字段的信息,以及对单独字段的动态访问。

这里就不再对具体的方法进行介绍了,读者有兴趣可以参考官方 API

这里只介绍几个常用的方法

equals(Object obj)

属性与obj相等则返回true

get(Object obj)

获得obj中对应的属性值

set(Object obj, Object value)

设置obj中对应属性值

Method 类

invoke(Object obj, Object... args)

传递object对象及参数调用该对象对应的方法

ClassLoader 类

反射中,还有一个非常重要的类就是 ClassLoader 类,类装载器是用来把类(class) 装载进 JVM的。ClassLoader 使用的是双亲委托模型来搜索加载类的,这个模型也就是双亲委派模型。ClassLoader 的类继承图如下

微信图片_20220414190210.png

枚举

枚举可能是我们使用次数比较少的特性,在 Java 中,枚举使用 enum 关键字来表示,枚举其实是一项非常有用的特性,你可以把它理解为具有特定性质的类。enum 不仅仅 Java 有,C 和 C++ 也有枚举的概念。下面是一个枚举的例子。

public enum Family {
    FATHER,
    MOTHER,
    SON,
    Daughter;
}

上面我们创建了一个 Family的枚举类,它具有 4 个值,由于枚举类型都是常量,所以都用大写字母来表示。那么 enum 创建出来了,该如何引用呢?

public class EnumUse {
    public static void main(String[] args) {
        Family s = Family.FATHER;
    }
}

枚举特性

enum 枚举这个类比较有意思,当你创建完 enum 后,编译器会自动为你的 enum 添加 toString() 方法,能够让你方便的显示 enum 实例的具体名字是什么。除了 toString() 方法外,编译器还会添加 ordinal() 方法,这个方法用来表示 enum 常量的声明顺序,以及 values() 方法显示顺序的值。

public static void main(String[] args) {
  for(Family family : Family.values()){
    System.out.println(family + ", ordinal" + family.ordinal());
  }
}

enum 可以进行静态导入包,静态导入包可以做到不用输入 枚举类名.常量,可以直接使用常量,神奇吗? 使用 ennum 和 static 关键字可以做到静态导入包

微信图片_20220414190215.png


上面代码导入的是 Family 中所有的常量,也可以单独指定常量。

枚举和普通类一样

枚举就和普通类一样,除了枚举中能够方便快捷的定义常量,我们日常开发使用的 public static final xxx 其实都可以用枚举来定义。在枚举中也能够定义属性和方法,千万不要把它看作是异类,它和万千的类一样。

public enum OrdinalEnum {
    WEST("live in west"),
    EAST("live in east"),
    SOUTH("live in south"),
    NORTH("live in north");
    String description;
    OrdinalEnum(String description){
        this.description = description;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public static void main(String[] args) {
        for(OrdinalEnum ordinalEnum : OrdinalEnum.values()){
            System.out.println(ordinalEnum.getDescription());
        }
    }
}

一般 switch 可以和 enum 一起连用,来构造一个小型的状态转换机。

enum Signal {
  GREEN, YELLOW, RED
}
public class TrafficLight {
    Signal color = Signal.RED;
    public void change() {
        switch (color) {
        case RED:
            color = Signal.GREEN;
            break;
        case YELLOW:
            color = Signal.RED;
            break;
        case GREEN:
            color = Signal.YELLOW;
            break;
        }
    }
}

是不是代码顿时觉得优雅整洁了些许呢?

枚举神秘之处

在 Java 中,万事万物都是对象,enum 虽然是个关键字,但是它却隐式的继承于 Enum 类。我们来看一下 Enum 类,此类位于 java.lang 包下,可以自动引用。

微信图片_20220414190220.png

此类的属性和方法都比较少。你会发现这个类中没有我们的 values 方法。前面刚说到,values() 方法是你使用枚举时被编译器添加进来的 static 方法。可以使用反射来验证一下

除此之外,enum 还和 Class 类有交集,在 Class 类中有三个关于 Enum 的方法

微信图片_20220414190224.png

前面两个方法用于获取 enum 常量,isEnum 用于判断是否是枚举类型的。

枚举类

除了 Enum 外,还需要知道两个关于枚举的工具类,一个是 EnumSet ,一个是 EnumMap

EnumSet 和 EnumMap

EnumSet 是 JDK1.5 引入的,EnumSet 的设计充分考虑到了速度因素,使用 EnumSet 可以作为 Enum 的替代者,因为它的效率比较高。

EnumMap 是一种特殊的 Map,它要求其中的 key 键值是来自一个 enum。因为 EnumMap 速度也很快,我们可以使用 EnumMap 作为 key 的快速查找。

总的来说,枚举的使用不是很复杂,它也是 Java 中很小的一块功能,但有时却能够因为这一个小技巧,能够让你的代码变得优雅和整洁。

I/O

创建一个良好的 I/O 程序是非常复杂的。JDK 开发人员编写了大量的类只为了能够创建一个良好的工具包,想必编写 I/O 工具包很费劲吧。

IO 类设计出来,肯定是为了解决 IO 相关操作的,最常见的 I/O 读写就是网络、磁盘等。在 Java 中,对文件的操作是一个典型的 I/O 操作。下面我们就对 I/O 进行一个分类。

微信图片_20220414190229.png

公号回复 IO获取思维导图

I/O 还可以根据操作对象来进行区分:主要分为

微信图片_20220414190232.png

除此之外,I/O 中还有其他比较重要的类

File 类

File 类是对文件系统中文件以及文件夹进行操作的类,可以通过面向对象的思想操作文件和文件夹,是不是很神奇?

文件创建操作如下,主要涉及 文件创建、删除文件、获取文件描述符等

class FileDemo{
   public static void main(String[] args) {
       File file = new File("D:\\file.txt");
       try{
         f.createNewFile(); // 创建一个文件
         // File类的两个常量
         //路径分隔符(与系统有关的)<windows里面是 ; linux里面是 : >
        System.out.println(File.pathSeparator);  //   ;
        //与系统有关的路径名称分隔符<windows里面是 \ linux里面是/ >
        System.out.println(File.separator);      //  \
         // 删除文件
         /*
         File file = new File(fileName);
         if(f.exists()){
             f.delete();
         }else{
             System.out.println("文件不存在");
         }   
         */
       }catch (Exception e) {
           e.printStackTrace();
       }
    }
}

也可以对文件夹进行操作

class FileDemo{
  public static void main(String[] args) {
    String fileName = "D:"+ File.separator + "filepackage";
    File file = new File(fileName);
    f.mkdir();
  // 列出所有文件
    /*
    String[] str = file.list();
    for (int i = 0; i < str.length; i++) {
      System.out.println(str[i]);
    }
    */
    // 使用 file.listFiles(); 列出所有文件,包括隐藏文件
    // 使用 file.isDirectory() 判断指定路径是否是目录
  }
}

上面只是举出来了两个简单的示例,实际上,还有一些其他对文件的操作没有使用。比如创建文件,就可以使用三种方式来创建

File(String directoryPath);
File(String directoryPath, String filename);
File(File dirObj, String filename);

directoryPath 是文件的路径名,filename 是文件名,dirObj 是一个 File 对象。例如

File file = new File("D:\\java\\file1.txt");  //双\\是转义
System.out.println(file);
File file2 = new File("D:\\java","file2.txt");//父路径、子路径--可以适用于多个文件的!
System.out.println(file2);
File parent = new File("D:\\java");
File file3 = new File(parent,"file3.txt");//File类的父路径、子路径
System.out.println(file3);

现在对 File 类进行总结

微信图片_20220414190238.png

基础 IO 类和相关方法

虽然. IO 类有很多,但是最基本的是四个抽象类,InputStream、OutputStream、Reader、Writer。最基本的方法也就是 read()write() 方法,其他流都是上面这四类流的子类,方法也是通过这两类方法衍生而成的。而且大部分的 IO 源码都是 native 标志的,也就是说源码都是 C/C++ 写的。这里我们先来认识一下这些流类及其方法

InputStream

InputStream 是一个定义了 Java 流式字节输入模式的抽象类。该类的所有方法在出错条件下引发一个IOException 异常。它的主要方法定义如下

微信图片_20220414190242.png

OutputStream

OutputStream 是定义了流式字节输出模式的抽象类。该类的所有方法返回一个void 值并且在出错情况下引发一个IOException异常。它的主要方法定义如下

微信图片_20220414190246.png

Reader 类

Reader 是 Java 定义的流式字符输入模式的抽象类。类中的方法在出错时引发 IOException 异常。

微信图片_20220414190250.png

Writer 类

Writer 是定义流式字符输出的抽象类。所有该类的方法都返回一个 void 值并在出错条件下引发 IOException 异常

微信图片_20220414190253.png

相关文章
|
3月前
|
小程序 JavaScript Java
【资料】阿里Java开发手册
本文是关于分享阿里Java开发手册资源及促进编程规范学习的指南。作者以个人经历引入,讲述了公司领导通过细致讲解阿里Java开发手册,提升了团队对代码质量和编程规范的认识
785 0
【资料】阿里Java开发手册
|
3月前
|
存储 Java 测试技术
阿里巴巴java开发手册
这篇文章是关于阿里巴巴Java开发手册的整理,内容包括编程规约、异常日志、单元测试、安全规约、MySQL数据库使用以及工程结构等方面的详细规范和建议,旨在帮助开发者编写更加规范、高效和安全的代码。
|
6月前
|
Java
电子书阅读分享《Java开发手册(嵩山版)》
电子书阅读分享《Java开发手册(嵩山版)》
|
6月前
|
Java
Java开发手册之控制语句,2024最新Java笔经
Java开发手册之控制语句,2024最新Java笔经
|
6月前
|
Java
电子书阅读分享《Java开发手册(泰山版)》
电子书阅读分享《Java开发手册(泰山版)》
电子书阅读分享《Java开发手册(泰山版)》
白瞟党乐坏了!Alibaba内部最新Java开发手册(嵩山版)灵魂17问
Java是世界各地开发者使用最多的编程语言,无论是在用户最喜爱的编程语言排行榜、程序员薪资榜单、编程入门首选语言等榜单上都是常年占据前三的位置的一种语言,但它也是最难学的语言之一。而《Java开发手册》可以算是学习Java,规范写法的必读书目了,那么你知道为什么要按照规约来吗?
|
6月前
|
SQL Java API
《Java开发手册灵魂13问》正式上线,带你剖析阿里巴巴的开发细节
一线大厂怎么用Java?看阿里技术专家给你分析!《〈Java开发手册(泰山版)〉灵魂13问》电子书正式上线带你剖析阿里巴巴一线团队开发思维。
|
存储 缓存 Java
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
230 0
Java基础知识第二讲:Java开发手册/JVM/集合框架/异常体系/Java反射/语法知识/Java IO
|
11月前
|
Java Apache Spring
Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer 那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢
Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer 那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢
103 0
|
Java
无意中发现阿里巴巴Java开发手册「2023最新黄山版」竟然发布了
提起阿里巴巴的《Java开发手册》大家肯定都不陌生,这份手册代表这Alibaba技术团队的集体智慧结晶和内部大佬的经验总结,经历了多次打磨不断的完善,随着市面上各种版本的流出,小编无意中发现了这份【黄山版】。
7155 1