第二章 Java基础

141 阅读8分钟

集合

Java的集合类被定义在java.util包中,主要有4种集合,包括List, Queue, Set, Map

List 可重复

List是一个非常常用的数据类型,是有序Collection,它的三个实现类分别为ArrayList,LinkedList和Vector。

  1. ArrayList:基于数组实现的List,增删慢,查询快,线程不安全
  2. Vector:基于数组实现,增删慢,查询快,线程安全
  3. LinkedList:基于双链表实现,增删快,查询慢,线程安全

Queue

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列
  2. LinkedBlockingQueue:基于链表实现的有界阻塞队列
  3. PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  4. DelayQueue:支持延迟操作的无界阻塞队列
  5. SynchronousQueue:用于线程同步的阻塞队列
  6. LinkedTransferQueue:基于链表实现的无界阻塞队列
  7. LinkedBlockingDeque:基于链表结构实现的双向阻塞队列

Set 不可重复

  1. HashSet:HashTable实现,无序
  2. TreeSet:二叉树实现,有序,自定义的数据类型必须实现Comparable接口,并覆写其中的compareTo方法,升序返回-1, 降序返回1
  3. LinkHashSet:HashTable实现数据存储,双向链表记录顺序

Map

HashMap:数组 + 链表存储数据,线程不安全。

如果需要满足线程安全的条件,可以使用Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

HashMap的数据结构:内部是一个数组,数组中的每一个元素都是Entry的实例,包含四个属性:key,value,hash值和用于指向单向链表下一个元素的next。

常用的参数:

  1. capacity:当前数组的容量,默认为16,可以扩容,扩容后大小为原来的两倍
  2. loadFactor:负载因子,默认为0.75
  3. threshold:扩容的阀值,为capacity * loadFactor

HashMap在查询数据的时候,根据hash值可以直接找到数据所在的数组下标,但是之后需要遍历该数组下的整个链表来查询数据,因此需要O(n)的时间。

为了减少链表遍历的时间,Java8对HashMap进行了优化,将数据结构修改为数据 + 链表或数组 + 红黑树,在链表的元素超过8个之后,HashMap会将链表结构转化为红黑树结构以提高查询效率。

ConcurrentHashMap:分段锁实现,线程安全

concurrencyLevel参数表示并行级别:默认为16,在这种情况下最多支持16个线程并发执行写操作,该值在初始化可以设置,一旦初始化后不能修改。

HashTable:线程安全

线程安全,但是同一时刻只有一个线程能执行写操作,并发性不如ConcurrentHashMap。

TreeMap:基于二叉树实现

在使用TreeMap时,其key必须实现了Comparable接口或采用自定义的比较器,否则会抛出java.lang.ClassCastException。

LinkedListHashMap:使用链表保存插入顺序

异常分类及处理

在Java中,Throwable是所有错误或异常的父类,Throwable又可以分为Error和Exception。

Error是指Java运行错误,如果程序在启动时出现Error,则启动失败,如果在运行时出现Error则安全退出进程。Error出现的原因一般是系统内部错误或者资源耗尽,系统也不能在运行时动态处理错误,能做的只是记录错误的成因和安全退出。

Exception是指Java运行异常,即在程序运行中发生了人们不期待的事件,可以被Java异常处理机制处理。Exception分为RuntimeException(运行时异常)和CheckedException(检查异常)

  1. RuntimeException:指在Java虚拟机正常运行期间抛出的异常,RuntimeException可以被俘获被处理
  2. CheckedException:在编译阶段Java编译器会检查CheckedException异常并强制俘获并处理此类异常,如IOException,SQLException,ClassNotFoundException等

反射机制

反射机制是在程序运行过程中,对任意一个类都能获取其所有属性和方法,并且对任意一个对象都能调用其任意一个方法。这种动态获取类和对象的信息,以及动态调用对象的方法的功能称为Java的反射机制。

Java中的对象有两种类型:编译时类型和运行时类型。编译时类型是指在声明对象时所采用的类型,运行时类型是指给对象赋值时采用的类型。

Java的反射API

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

反射的步骤

  1. 获取想要操作的类的Class对象,该类是反射的核心
  2. 调用Class对象所对应的类中定义的方法,这是反射的使用阶段
  3. 使用反射API来获取并调用类的属性和方法等信息

获取Class对象的3种方法:

// 调用某个对象的getClass方法
Person p = new Person();
Class clazz = p.getClass();
// 调用某个类的class属性
Class clazz = Person.class();
// 调用Class类中的静态方法forName方法,这是最安全,性能最高的方法
Class clazz = Class.forName("fullClassPath"); // 全类名

在获得类的Class对象后,可以通过Class类中的方法获取并查看该类的方法和属性

// 获取Person类的Class对象
Class clazz = Class.forName("fullClassPath");
// 获取Person类的所有方法的信息
Method[] methods = clazz.getDeclaredMethods();
// 获取Person类所有成员的属性信息
Field[] fields = clazz.getDeclaredFields();
// 获取Person类所有构造方法的信息
Constructor[] constructors = clazz.getDeclaredConstructors();

创建对象的两种方法

// 使用Class对象的newInstance方法,该方法要求该Class对象对应的类有空参的构造函数

// 使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法创建Class对象对应的实例
Class clazz = Class.forName("fullClassPath");
Constructor c = clazz.getDeclaredConstructor(String.class, String.class);
Person p = (Person) c.newInstance("大锤", "男");

Method的invoke方法

Class clz = Class.forName("fullClassPath");
Method method = clz.getMethod("setName", String.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "dachui");

注解

注解的概念

注解是(Annotation)Java提供的设置程序中元素的关联信息和元数据的方法,它是一个接口,程序可以通过反射获取指定程序中的元素的注解对象,从而通过该注解对象获取注解中的元数据信息。

标准元注解

元注解负责注解其他注解。

@Target
// 说明了注解所修饰的对象范围
@Retention
// 定义了该注解被保留的级别
// SOURCE:在源文件中被保留
// CLASS:在Class文件中被保留
// RUNTIME:在运行时被保留
@Documented
// 表明该注解应该被javadoc工具记录
@Inherited
// 表明某个被标注的类型是被继承的

注解处理器

定义注解接口

@Traget(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
	// 供应商编号
    public int id() default -1;
    // 供应商名称
    public String name() default "";
    // 供应商地址
    public String address() default "";
}

使用注解接口

public Apple {
	// 使用注解接口
    @FruoitProvider(id = 1, name = "陕西红富士", address = "陕西省西安市")
    private String appleProvider;
    
    // setter()...
    // getter()...
}

定义注解处理器

public class FruitInfoUtil {
	public static void getFruitInfo(Class<?> clazz) {
    	Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields) {
        	if(field.isAnnotaionPresent(FruitProvider.class)) {
            	FruitProvider fruitProvider = (FruitProvider) field.getAnnotion(FruitProvider.class);
                System.out.println("编号:" + fruitProvider.id() + "地址:" + fruitProvider.address())
            }
        }
    }
    
    public static void main(String[] args) {
    	FruitInfoUtil.getFruitInfo(Apple.class);
    }
}

内部类(编程思想补充)

定义在类内部的类称为内部类,按照定义的方法的不同,可以分为静态内部类,成员内部类,静态内部类和匿名内部类。

静态内部类

静态内部类可以访问外部类的静态变量和方法;在静态内部类中可以定义静态变量,方法,构造函数等。

Java集合类HashMap在内部维护了一个静态内部类Node数组用于存放元素,当Node数据对于使用者是透明的。像这种和外部类关系密切且不依赖外部类实例的类,可以使用静态内部类实现。

成员内部类

成员内部类不用定义静态方法和变量(final修饰的除外)。

局部内部类

定义在方法中的类叫做局部内部类,当一个类只需要在某个方法中使用时,可以通过局部类来优雅地实现。

匿名内部类

指通过继承一个父类或者实现一个接口的方式定义并使用的类

泛型

泛型的本质是参数化类型。

在不使用泛型的情况下,我们可以使用引用Object类来实现参数的任意化。

使用泛型的好处是在编译期就能够检查类型是否安全,同时所有强制性类型转换都是自动和隐式进行的,提高了代码的安全性和重用性。

泛型标记和泛型限定:E,T,K,V,N,?

序号泛型标记说明
1E-Element在集合中使用,表示在集合中存放的元素
2T-Type表示Java类
3K-Key表示键
4V-Value表示值
5N-Number表示数据类型
6?表示不确定的Java类型

类型擦除

在编码阶段才同泛型时加上的类型参数,会被编译器在编译时去掉,这个过程叫做类型擦除。

首先,查找用来替换类型参数的具体类(一般为Object),如果定义了类型参数的上界,则使用该上界,然后将代码中所有的类型参数都替换成具体的类。

序列化(待补充原理)

类要实现序列化功能,只需实现java.io.Serializable接口即可。

序列化并不保存以及transient修饰的变量

可以使用基于JDK原生的ObjectOutputStream和ObjectInputStream实现对象的序列化和反序列化,并调用writeObject和readObject方法实现自定义序列化策略。