Java的集合类被定义在Java.util包中,主要有4种集合,分别为LIst,Queue,Set,Map,每种集合的具体分类如下:
List是非常常用的数据类型,是有序的Collection,一共有三个实现类,分别是ArrayList,Vector和LinkedList。
1.Array List :基于数组实现,增删慢,查询快,线程不安全
array list是使用最狂饭的List实现类,其内部数据结构基于数据组实现,提供了对List的增加(add),删除(remove)和访问(get)功能。
ArrayList的缺点是元素必须连续存储,当需要在ArrayList的中间的位置插入或者删除袁术是,需要将待插入或删除的节点后的所有元素进行移动,其修改代价较高,
因此,Array List不适合随机插入和删除的操作,更适合随机查找和遍历的操作
Array List不需要在定义时指定数组的长度,在数据长度不能满足存储要求时,Array List会创建一个新的更大的数组并将数据种已有的数据复制到新的数组中。
2.Vector:基于数组实现,增删慢,查询快,线程安全
Vector的数据结构和ArrayList一样,都是基于数组实现的,不同的是Vector支持线程同步,及同一时刻只允许一个线程对Vector进行写操作(新增,删除,修改),以保证多线程环境下数据的一致性,但需要频繁地对Vector实例进行加锁和释放锁操作,因此,Vector的读写效率上比ArrayList低。
3.linkedList:基于双向链表实现,增删快查询慢,线程不安全
LinkedList采用双向链表结构存储元素,在对LinkedList进行插入和删除操作时,只需要在对应的节点上插入或 删除元素,并将上一个节点元素的下一个节点的指针指向该节点即可,数据改动较小,因此随机插入和删除效率很高。
但在对Linked List进行随机访问时,需要从链表头部一直遍历到该节点为止,因此随机访问速度很慢。
除此之外,LinkedList提供了在List接口中未定义的方法,用于操作链表头部和尾部的元素,因此有时可以被当作堆栈和队列使用。
queue是队列结构,Java中常用队列如下:
ArrayBlockingQueue:基于数组数据结构实现的有界阻塞队列
LinkedBlockingQueue:基于链表数据结构实现的有界阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:支持言辞操作的无界阻塞队列
SynchronizedQueue:用于线程同步的阻塞队列
LinkedTransferQueue:基于链表数据结构实现的无界阻塞队列
LinkedBlockingDeque:基于链表数据结构实现的双向阻塞队列
Set的核心特性是独一无二,使用与存储无序且值不相等的元素。
对象的相等性在本质上是对象的Hash Code值相同,Java依据对象的内存地址计算出对象的HashCode值。
如果想要比较两个对象是否相等,则必须同时覆盖对象的hashCode方法和equals方法,并且hashCode方法和equals方法的返回值必须相同。
1.HashSet:HashMap实现,无序
HashSet存放的是散列值,他是按照元素的散列值来存取元素的。
元素的散列值是通过元素的HashCode方法计算得到的,HashSet首先判断两个元素的散列值是否相等,如果散列值相等,则接着通过equals方法比较,如果equals方法返回的结果也为true,HashSet就将其是为同一个元素;如果equals方法返回的结果为false,HashSet将其视为不同的元素。
2.TreeSet:二叉树实现
TreeSet基于二叉树的原理对新添加的对象按照指定的顺序排序(升序,降序),每添加一个对象都会进行排序,并将对象插入二叉树的指定位置 。
Integer和String等基础对象类型可以直接根据TreeSet的默认排序精选存储,而自定义的数据类型必须实现Comparable接口,并且覆写其中的compareTo函数才可以按照预定义的顺序存储。
若覆写compare函数,则在升序时在this.对象小于指定对象的条件下返回-1,在降序时在this.对象大于指定对象的条件下返回1.
3.LinkedHashSet:继承HashSet,HashMap实现数据存储,双向链表记录顺序
Linked Hash Set在底层使用LinkedHashMap存储元素,它继承了HashSet,所有的方法和操作都与Hash Set相同,因此LinkedHashSet的实现比较简单,只提供了4个构造方法,并通过传递一个标识参数调用父类的构造器,在底层构造一个LinkedHashMap来记录数据访问,其他相关操作与父类HashSet相同,直接调用父类HashSet的方法即可。
1.HashMap:数组+链表存储数据,线程不安全
HashMap基于键的HashCode值为一标识一条数据,同时基于键的HashCode值进行数据的存取,
因此可以快速地更新和查询数据,但其每次遍历的顺序都无法保证相同。
HashMap的key和value允许为null。
HashMap是非线程安全的,即在同一时刻有多个线程同时写HashMap时将可能导致数据的不一致。
如果需要满足线程安全的条件,则可以用Collections的synchronizedMap方法时HashMap对线程安全的能力,或者使用ConcurrentHashMap。
HashMap数据结构,其内部是一个数组,数组中的每个元素都是一个单项链表。
链表中的每个元素都是嵌套类Entry的实例,Entry实例包含4个属性:key,value,hash值和用于指向单向链表下一个元素的next。
HashMap常用的参数:
capacity:当前数组的容量,默认为16,可以扩容,扩容数组大小为当前的两倍。
loadFactor:负载因子,默认为0.75。
threshold:扩容的阈值,其值等于capacity*loadFactor。
HashMap在查找数据时,根据HashMap的Hash值可以快速定位到数组的具体下标,
但是在找到数组下标后需要对链表进行顺序遍历直到找到需要的数据,时间复杂度为O(n)。
为了减少遍历的开销,Java8对HashMap进行了优化,将数据结构修改为数组+链表或红黑树。
在链表中的元素超过8个以后,HashMap会将链表结构转换为红黑树结构,以提高查询效率,
因此其时间复杂度为O(log N)。
2.ConcurrentHashMap:分段锁实现,线程安全
与Hash Map不同。ConcurrentHashMap采用分段锁的思想实现并发操作,因此线程是安全的。
Concurrent Hash Map有多个Segment组成(Segment的数量也是锁的并发度),
每个Segment均继承自ReentrantLock并单独加锁,所以每次进行加锁操作时锁住的都是一个Segment,
这样只要保证每个Segment都是线程安全的,也就实现类全局的线程安全。
Concurrent Hash Map的数据结构:
在ConcurrentHashMap中有个concurrencyLevel参数表示并行级别,默认是16,也就是说ConcurrentHashMap默认由16个Segments组成,在这种情况下最多同时支持16个线程并发执行写操作,只要他们的操作在不同的segment上即可。
并行级别concurrentLevel可以在初始化时设置,一但初始化就不可更改。
ConcurrentHashMap的每个Segment内部的数据结构都和HashMap相同。
Java8在ConcurrentHashMap中引入了红黑树,数据结构如下:
3.HashTable:线程安全
HashTable是遗留类,很多映射的常用功能都与Hash Map类似,不同的是它继承自Dictionary类,并且线程安全的,同一时刻只有一个线程能写Hash Table,并发性不如Concurrent Hash Map。
4.Tree Map:基于二叉树数据结构
TreeMap基于二叉树数据结构存储数据,同时实现了SortedMap接口 以保障元素的顺序存取,默认按键值的升序排序,也可以自定义排序比较器。
TreeMap常用与实现排序的映射列表。
在使用TreeMap时其key必须实现Comparable接口 或 采用自定义的比较器 ,否则会抛出java.lang.ClassCastException异常。
5.LinkedHashMap:继承HashMap,使用链表保存插入顺序
LinkedHashMap为HashMap的子类,其内部使用链表保存元素是的插入顺序,当通过Iterator遍历LinkedHashMap时,会按照元素的插入顺序访问元素。
异常值在方法不能按照正常方式完成时,可以通过抛出异常的方式会退出该方法,在异常中封装了方法执行过程中的错误信息及原因,调用方在获取该异常后可根据业务的情况选择处理该异常或者继续抛出该异常。
在方法的执行过程中出现异常时,Java异常处理机制会将代码的执行权交给异常处理器,
异常处理器根据在系统中定义的异常处理规则执行不同的异常处理逻辑(抛出异常 或 捕捉并处理异常)。
在Java中,Throwable时所有错误或异常的父类,Throwable又可分为Error和Exception,
常见的Error有AWTError,ThreadDeath,
Exception又可分为RuntimeException和CheckedException。
Error指Java程序运行错误,如果程序在启动时出现Error,则启动失败;
如果程序在运行此过程中出现Error,则系统将退出进程。
吹嘘那Error通常是因为系统内部错误或资源耗尽,Error不能被在运行过程中被动态代理。
如果程序出现Error,则系统能做的工作也只能有 记录错误的成因 和 安全终止。
Exception指Java程序运行异常,即运行中的程序发生了人们不期望发生的事件,可以被Java异常处理机制处理。
Exception也是程序开发中异常处理的核心,可分为RuntimeException(运行时异常)和CheckedException(检查异常)。
RuntimrException:指在Java虚拟机正常运行期间抛出的异常,RuntimeException可以被捕获并处理,如果出现RuntimeException,那么一定是程序发生错误导致的。
常见的RuntimeException有NullPointerException,ClassCastException,ArrayIndexOutOfBundsException等。
CheckedException:指在编译阶段Java编译器会检查Checked Exception异常 并 强制程序捕获并处理异常,即要求程序在可能出现异常的地方通过try catch居于块捕获并处理此类异常。
常见的CheckedException有 由于I/O错误导致的IOException,SQLException,ClassNotFoundException等。
该类异常一般由于打开错误的文件,sql语法错误,类不存在等引起。
抛出异常,使用try catch语句块捕获并处理异常这两种方式。
(1)抛出异常:
抛出异常有三种形式:throws,throw,系统自动抛出异常。
throws作用在方法上,用于定义方法可能抛出的异常。
throw作用在方法内,表示明确抛出一个异常。
(2)使用try catch捕获并处理异常:
使用try catch捕获异常能够有针对性地处理每种可能出现的异常,并在捕获到异常后根据不同的情况做不同的处理。
指程序在运行是可以改变其结构的语言。
从反射的角度来说。Java属于半动态语言。
指在程序运行过程中,对任意一个类都能获取其所有属性和方法,并且对任意一个对象都能调用其任意一个方法。
Java中的对象有两种类型:编译时类型和运行时类型。
编译时类型指在声明对象时所采用的类型,
运行时类型指为对象赋值时所采用的类型。
反射API主要用于在运行过程中动态生成类,接口或对象等信息,
其常用API:
Class类:用于获取类的属性,方法等信息。
Field类:表示雷达成员变量,用于获取和设置类中的属性值。
Method类:表示类的方法,用于获取方法的描述信息或者执行某个方法。
Constructor类:表示类的构造方法。
1.获取想要操作的类的Class对象,该Class对象是反射的核心,通过它可以调用类的任意方法。
2.调用Class对象所对应的类中定义的方法
3.使用反射API来获取并调用类的属性和方法等信息
获取Class对象的3种方法:
1.调用某个对象达getClass方法已获取该类对应的Class对象:
Person p = new Person(); Class clazz = p.getClass();2.调用某个类的class属性以获取该类对应的Class对象:
Class clazz = Person.class();3.调用Class类种的forName静态方法以获取该类对应的Class对象,这是最安全,性能也最好的方法:
Class clazz = Class.forName("hello.java.reflect.Person");//类的包路径及名称获取想要操作的类的Class对象后,可以通过Class类种的方法获取并查看该类种的方法和属性:
//1获取 pp 类的Class对象 Class clazz = Class.forName("hello.pp"); //Class clazz = Person.class; //2获取pp类的所有方法的信息 Method[] methods = clazz.getDeclaredMethods(); for(Method m:methods){ System.out.println(m.toString()); } //3获取pp类的所有成员的属性信息 Field[] fields = clazz.getDeclaredFields(); for(Field f:fields){ System.out.println(f.toString()); } //4获取pp类的所有构造方法的信息 Constructor[] constructors = clazz.getDeclaredConstructors(); for(Constructor c:constructors){ System.out.println(c.toString()); }1.使用Class对象的newInstance方法创建该Class对象对应类的实例,这种方法要求该Class对象对应的类有默认的空构造器。
2.先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance方法创建Class对象对应类的实例,通过这种方法可以选定构造方法创建实例
try { //1.1获取Person类的Class对象 Class clazz = Class.forName("Person"); //1.2使用newInstane方法创建对象 Person person = (Person) clazz.newInstance(); //2.1获取构造方法并创建对象 Constructor constructor = clazz.getDeclaredConstructor(int.class,String.class); //2.2根据构造方法创建对象并设置属性 Person person1 = (Person) constructor.newInstance(20,"lly"); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }Method提供了关于类或接口上某个方法及如何访问该方法的信息,
那么在运行的代码种如何动态调用该方法呢?
答案就通过调用Method的invoke方法。
我们通过invoke方法可以实现动态调用,比如可以动态传入参数并将方法参数化。
具体过程为:获取对象的Method,并调用Method的invoke方法。
(1)获取Method对象:
通过调用Class对象的getMethod(String name,Class<?>…parameterTypes)返回一个Method对象,
它描述了此Class对象所表示的类或接口指定的公共成员方法。
name参数是String类型,用于指定所需方法的名称。
parameterTypes参数是按声明顺序表示该方法的形参类型的Class对象的一个数组,
(2)调用invoke方法:
指通过调用Method对象的invoke方法来动态执行函数。
invoke方法的具体使用代码:
//step 1:获取Person类的class对象 Class clz = Class.forName("Person"); //step 2:获取class对象种setName方法 Method method = clz.getMethod("setName",String.class); //step 3:获取Constructor对象 Constructor constructor =clz.getConstructor(); //step 4:根据Constructor定义对象 Object object = constructor.newInstance(); //step 5:调用method的invoke方法,这里的method表示setName方法 //因此,相当于动态调用object对象的setName方法并传入lllly参数 method.invoke(object,"lllly");注解(Annotation)是Java提供的设置程序种元素的关联信息和元数据(MetaData)的方法
他是一个接口,
程序可以通过反射获取指定程序种的元素的注解对象,然后通过该注解对象获取注解中的元数据信息
@Target,@Retention,@Documented,@Inherited
元注解负责注解其他注解。
1,@Target:说明了注解所修饰的对象范围。
注解可被用于
packages,
types(类,接口,枚举,注解类型),
类型成员(方法,构造方法,成员变量,枚举值),
方法参数和本地变量(循环变量,catch参数等)
target的具体取值:
2,@Retention:定义了该注解被保留的级别,即被描述的注解在什么级别有效。
有3种类型:
SOURCE:在源文件种有效
CLASS:在Class文件种有效
RUNTIME:在运行时有效
3,@Documented:表明这个注解应该被javadoc工具记录,因此可以被javadoc类的工具文档化。
4,@Inherited:是一个标记注解,表明某个被标注的类型是被继承的。
如果有一个使用了@Inherited修饰的Annotation被用于一个Class,则这个注解将被用与该Class的子类
注解用于描述元数据的信息,使用的重点在于对注解处理器的定义。
Java SE5扩展了反射机制的API,以帮助程序快速构造自定义注解处理器。
对注解的使用一般包含定义及使用注解接口,我们一般通过封装统一的注解工具来使用注解。
1.定义注解接口
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider{ //编号 public int id() default -1; //名称 public String name() default ""; //地址 public String address() default ""; }2.使用注解接口
public class Apple{ @FruitProvider(id = 1,name = "红富士",address = "西安") private String appleProvider; public void setAppleProvider(String appleProvider){ this.appleProvider =appleProvider; } public String getAppleProvider(){ return appleProvider; } }3.定义注解处理器
public class FruitInfoUtil { public static void getFruitInfo(Class<?> clazz) { String strFruitProvicer = "供应商信息:“"; Field[] fields = clazz.getDeclaredFields(); for(Field field:fields){ if(field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider = (FruitProvider)field.getAnnotation(FruitProvider.class); strFruitProvicer = "供应商编号: " + fruitProvider.id() + "供应商名称: " + fruitProvider.name() + "供应商地址: " + fruitProvider.address(); System.out.println(strFruitProvicer); } } } } public class FruitRun { public static void main(String[] args) { FruitInfoUtil.getFruitInfo(Apple.class); } }定义在类内部的类被称为内部类。
内部类根据不同的定义方式,可分为静态内部类,成员内部类,局部内部类和匿名内部类
泛型的本质是参数化类型,泛型提供了编译时类型的安全检测机制,该机制允许程序在编译时检测非法的类型,
比如要实现一个能对字符串(String),整形(Int),浮点型(Float),对象(Object)进行大小比较的方法,就可以使用Java泛型。
类型通配符使用“?”表示所有具体的参数类型,
例如
List<?> 在逻辑上是List,List等 所有List<具体类型实参>的父类
1.对泛型上限的限定:<? extends T>
表示该通配符所代表的类型是 T类的子类 或者 接口T的子接口
2.对泛型下限的限定:<? super T>
表示该通配符所代表的类型是 T类型的父类或者父接口
泛型方法 指 将方法的参数类型定义为泛型
以便在调用时接收不同类型的参数。
在方法的内部根据传递给泛型方法的不同参数执行不同的处理方法
用法如下:
public class Study { public static void main(String[] args) { generalMenthod("1",2,new OuterClass()); } public static <T> void generalMenthod(T ... inputArray){ for(T element : inputArray){ if(element instanceof Integer){ System.out.println("处理Integer类型"); } else if(element instanceof String){ System.out.println("处理String类型"); } else if(element instanceof Double){ System.out.println("处理Double类型"); } else if(element instanceof Float){ System.out.println("处理Float类型"); } else if(element instanceof Long){ System.out.println("处理Long类型"); } else if(element instanceof Boolean){ System.out.println("处理Boolean类型"); } else if(element instanceof Date){ System.out.println("处理Date类型"); } else if(element instanceof Worker){ System.out.println("处理Worker类型"); } } } private static class OuterClass { } }泛型类指在定义类时在类上定义了泛型,
以便类在使用时可以根据传入的不同参数类似实例化不同的对象
泛型类的具体使用方法:
在类的名称后面添加一个或多个类型参数的声明部分,
多个泛型参数之间用逗号隔开。
具体用法如下:
public class GeneralClass<T> { public static void main(String[] args){ GeneralClass<Integer> genInt = new GeneralClass<Integer>(); genInt.add(1); genInt.get(); GeneralClass<String> genStr = new GeneralClass<String>(); genStr.add("2"); genStr.get(); } private T t; public void add(T t){ this.t = t; } public T get(){ System.out.println(t); return t; } }泛型接口的声明和泛型类的声明类似
通过接口名后面添加类型参数的声明部分来实现。
泛型接口的具体类型一般在实现类中进行声明,不同类型的实现类处理不同的业务逻辑。
具体实现如下:
public interface IGeneral<T> { public T getId(); } public class GeneralIntergerImpl implements IGeneral<Integer> { @Override public Integer getId() { Random random = new Random(100); return random.nextInt(); } public static void main(String[] args) { GeneralIntergerImpl gen = new GeneralIntergerImpl(); System.out.println(gen.getId()); } }在编码阶段使用泛型时加上的类型参数,会被编译器在编译时去掉,这个过程就被称为类型擦除。
因此,泛型主要用于编译阶段。在编译后生产的Java字节代码文件中不包含泛型的类型信息
Java类型的擦除过程为:
首先,查找用来替换类型参数的具体类(一般为Object),如果指定了类型参数的上界,则以该上界作为替换时的具体类:
然后,把代码中的类型参数都替换为具体的类。
Java对象在JVM运行时被创建,更新和销毁,
当JVM退出时,对象也会随之销毁,即这些对象的生命周期不会比JVM的生命周期更长
在现实应用中,我们常常需要将对象及其状态在多个应用之间传递,共享,或者将对象及其状态持久化,
在其他地方重新读取被保存的对象及其状态继续进行处理。
这就需要通过将Java对象序列化来实现。
在使用Java序列化技术保存对象及其状态信息时,对象及其状态信息会被保存在一组字节数组中,在需要时在将这些字节数组饭序列化为对象。
注意,对象序列化保存的是对象的状态,即对象的成员变量,因此类中的静态变量不会被序列化。
对象序列化处理用于持久化对象,在RPC(远程过程调用)或者网络传输中也经常被使用。
java序列化API为处理对象序列化提供了一个标准机制
Java序列化需要注意以下事项:
1.类要实现序列化功能,只需实现Java.io.Serializable接口即可。
2.序列化和反序列化必须保存序列化的ID一致,一般使用private static final long serialVersionUID 定义序列化ID。
3.序列化并不保存静态变量。
4.在需要序列化父类变量时,父类也需要实现Serializable接口
5.使用Transient关键字可以阻止该变量被序列化,在被饭序列化后,transient变量的值被设为对应类型的初始值。
具体代码如下:
public class Worker implements Serializable{ //定义序列化的ID private static final long sericalVersionUID = 123456789; //name属性将被序列化 private String name; //transient修饰的变量不会被序列化 private transient int salary; //静态变量属于类信息,不属于对象的状态,因此不会被序列化 static int age = 100; public String getName() { return name; } public void setName(String name) { this.name = name; } }对象通过序列化后在网络上传输时,基于网络安全,我们可以在序列化前将一些敏感信息字段使用秘钥进行加密,在反序列化后再基于秘钥对数据进行解密。
在Java生态中有很多优秀的序列化框架,比如arvo,protobuf,thrift,fastjson.
我们也可以基于jdk原生的ObjectOutputStream和ObjectInputStream类实现对象的序列化及反序列化,
并调用器writeObject和readObject方法实现自定义序列化策略
具体代码如下:
public static void main(String[] args) throws Exception { //序列化数据到磁盘 FileOutputStream fileOutputStream = new FileOutputStream("worker.out"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); Worker testObject = new Worker(); testObject.setName("ly"); objectOutputStream.writeObject(testObject); objectOutputStream.flush(); objectOutputStream.close(); //反序列化磁盘数据并解析数据状态 FileInputStream fileInputStream = new FileInputStream("worker.out"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Worker deTest = (Worker) objectInputStream.readObject(); System.out.println(deTest.getName()); }