最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

实习

IT圈 admin 46浏览 0评论

实习

基础语法

基本数据类型(熟悉)

Java有哪些数据类型
定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

boolean类型占多少个字节?

  • boolean类型被编译为int类型,等于是说JVM里占用字节和int完全一样,int是4个字节,于是boolean也是4字节
  • boolean数组在Oracle的JVM中,编码为byte数组,每个boolean元素占用8位=1字节(待考证)boolean数组在非Oracle JVM里面,占用字节数也许不是1字节


jdk8的官方文档解释

虽然Java虚拟机定义了一个boolean类型,但它只为它提供了非常有限的支持。没有Java虚拟机指令专门用于对boolean值的操作。相反,Java编程语言中对boolean值进行操作的表达式被编译为使用Java虚拟机int数据类型的值。

Java虚拟机直接支持boolean数组。它的newarray指令可以创建boolean数组。使用byte数组指令baload和bastore访问和修改类型为boolean的数组。

在Oracle的Java虚拟机实现中,Java编程语言中的boolean数组被编码为Java虚拟机byte数组,每个布尔元素使用8位。

Java虚拟机使用1表示boolean数组组件的true,0表示false。其中Java编程语言布尔值由编译器映射到Java虚拟机类型int的值,编译器必须使用相同的编码。

变量类型(熟悉)

访问修饰符(熟悉)

1、访问修饰符定义
Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

2、访问修饰符分类
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

protected : 对同一包内的类和不同包的所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

public : 对所有类可见。使用对象:类、接口、变量、方法

3、访问修饰符图

 面向对象

抽象

抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

封装

封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

专业描述:【隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。】

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

关于继承如下 3 点请记住:

1、子类拥有父类非 private 的属性和方法。

2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

3、子类可以用自己的方式实现父类的方法(重写)。

多态

所谓多态就是指一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

专业描述:【父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。】

在Java中有两种形式可以实现多态:继承(子类对父类同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

方法重载(overload)实现的是编译时的多态性(也称为前绑定)

方法重写(override)实现的是运行时的多态性(也称为后绑定)。


重载(Overload)和重写(Override)的区别?

  • 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
  • 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
  • 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表和返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
  • 重载对返回类型没有特殊的要求,不能根据返回类型进行区分。重写要求和父类被重写的方法有相同的返回类型

内部类的分类有哪些?


内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。

1、静态内部类

定义在类内部的静态类,就是静态内部类。

public class Outer {private static int radius =1;//静态内部类
static class StaticInner{ 
public void visit(){System.out.println("visit outer static variable:"+ radius);}}
}

静态内部类可以访问外部类所有的静态变量,但是不可访问外部类的非静态变量;

静态内部类的创建方式,new 外部类.静态内部类(),如下:

Outer.StaticInner inner =new Outer.StaticInner();inner.visit();

2、成员内部类

定义在类内部,成员位置上的非静态类,就是成员内部类。

public class Outer {
private static int radius =1;
privateint count =2;
class Inner{
public void visit(){    
System.out.println("visit outer static  variable:"+ radius);    
System.out.println("visit outer variable:"+ count);}}
}
成

成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。

成员内部类依赖于外部类的实例,它的创建方式外部类实例.new 内部类(),如下:

Outer outer =new Outer();Outer.Inner inner = outer.new Inner();inner.visit();

3、局部内部类

定义在方法中的内部类,就是局部内部类。

public class Outer{private int out_a =1;private static int STATIC_b =2;public void test FunctionClass(){int inner_c=3;class Inner{private void fun(){        System.out.println(out_a);    System.out.println(STATIC_b); System.out.println(inner_c);}}    Inner  inner =new Inner();    //直接在方法里用new创建局部内部类inner.fun();}public static void test StaticFunctionClass(){int d=3;class Inner{private void fun(){//System.out.println(out_a); //编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量System.out.println(STATIC_b);    System.out.println(d);}}    Inner  inner =new Inner();    //直接在方法里用new创建局部内部类inner.fun();}
} 

定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和静态方法 。

局部内部类的创建方式 ,在对应方法内 new 内部类(), 如下 :

public static void testStaticFunctionClass(){class Inner{} Inner inner =new Inner();
}

4、匿名内部类

匿名内部类就是没有名字的内部类

匿名内部类举例

 红色框画出来的就是一个匿名内部类同时重写了父类Animals的eat方法,并且调用了这个匿名内部类的eat方法

如何调用匿名内部类中的方法?

1、匿名内部类中只有一个方法的情况

2、匿名内部类有多个方法

第一种方式

 第二种方式

 如果想调用匿名内部类自己特有的方法的时候呢?该如何调用呢?

  

匿名内部类可以有自己特有的方法,但是前提条件是这个匿名内部类只有这一个方法(不重写父类或者接口的方法)。如果有多个方法的时候,他只能继承父类的方法以及重写这个方法或实现接口,绝不可能在在多个方法的情况下,调用自身特有的方法,但是这个特有的方法可以存在,但无法调用

匿名内部类是实现接口

 匿名内部类存在的前提是要有继承或者实现关系的,但是并没有看到extends和implements关键字,这是怎么回事呢?

答:很简单,匿名内部类没有连类名都没有,使用关键字就更无从说起了。这些由jvm搞定了。

匿名内部类的特点

除了没有名字,匿名内部类还有以下特点:

1、匿名内部类必须继承一个抽象类或者实现一个接口。

2、匿名内部类不能定义任何静态成员和静态方法。

3、匿名内部类访问局部变量的时候,必须把局部变量声明为 final。

4、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

匿名内部类创建方式:

new 类/接口{

//匿名内部类实现部分

}

什么是枚举?

枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。

提示:如果没有显式地声明基础类型的枚举,那么意味着它所对应的基础类型是 int。

下面代码定义了一个表示性别的枚举类型 SexEnum 和一个表示颜色的枚举类型 Color。

public enum SexEnum {male,female;
}
public enum Color {RED,BLUE,GREEN,BLACK;
}

之后便可以通过枚举类型名直接引用常量,如 SexEnum.male、Color.RED。

枚举类

Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public、 static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。

所有枚举实例都可以调用 Enum 类的方法,常用方法如表 1 所示。

 1、通过调用枚举类型实例的 values( ) 方法可以将枚举的所有成员以数组形式返回,也可以通过该方法获取枚举类型的成员。

下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 values() 方法输出这些成员。

enum Signal {// 定义一个枚举类型GREEN,YELLOW,RED;
}
public static void main(String[] args) {for(int i = 0;i < Signal.values().length;i++) {System.out.println("枚举成员:"+Signal.values()[i]);}
}

输出结果如下:

枚举成员:GREEN
枚举成员:YELLOW
枚举成员:RED

2、创建一个示例,调用valueOf() 方法获取枚举的一个成员,再调用 compareTo() 方法进行比较,并输出结果。具体实现代码如下:

public class TestEnum {public enum Sex {// 定义一个枚举male,female;}public static void main(String[] args) {compare(Sex.valueOf("male"));    // 比较}public static void compare(Sex s) {for(int i = 0;i < Sex.values().length;i++) {System.out.println(s + "与" + Sex.values()[i] + "的比较结果是:" + spareTo(Sex.values()[i]));}}
}

上述代码中使用 Sex.valueOf("male") 取出枚举成员 male 对应的值,再将该值与其他枚举成员进行比较。最终输出结果如下:

male与male的比较结果是:0
male与female的比较结果是:-1

3、通过调用枚举类型实例的ordinal() 方法可以获取一个成员在枚举中的索引位置。下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 ordinal() 方法输出成员及对应索引位置。

public class TestEnum1 {enum Signal {// 定义一个枚举类型GREEN,YELLOW,RED;}public static void main(String[] args) {for(int i = 0;i < Signal.values().length;i++) {System.out.println("索引" + Signal.values()[i].ordinal()+",值:" + Signal.values()[i]);}}
}

输出结果如下:

索引0,值:GREEN
索引1,值:YELLOW
索引2,值:RED

集合与泛型

ArrayList的使用

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

ArrayList是实现List接口的,底层采用数组实现。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

ArrayList集合的特点: (多用于查询)

1)集合数据存储的结构是数组结构。
2)元素增删慢,查找快

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

 

Java新手教程之ArrayList的基本使用_java_脚本之家

关于ArrayList的使用_菜鸟是鸟菜的博客-CSDN博客_arraylist怎么用

ArrayList常用方法总结_涟涟涟涟的博客-CSDN博客_arraylist方法

LinkedList的使用

LinkedList 实现了Queue接口,能当作队列使用。
LinkedList 实现了List 接口,能对它进行列表操作。
LinkedList 实现了Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,能克隆。
LinkedList 实现了java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList集合的特点: (多用于增删)

  1. 底层是一个链的结构:查询慢,增删快
  2. 里边包含了大量操作首尾元素的方法

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
常用API描述

add()

添加元素

get()

获取元素

addFirst(E e)

元素添加到头部

addLast(E e)

元素添加到尾部

clear()

清空链表

remove(int index)

删除指定位置的元素

indexOf()

查找指定元素从前往后第一次出现的索引

clone()

克隆列表

size()

返回链表元素个数

add(int index, E element)向指定位置插入元素
addAll(Collection<? extends E> c)将一个集合的所有元素添加到链表后面
addAll(int index, Collection<? extends E> c)将一个集合的所有元素添加到链表的指定位置后面
removeFirst()删除第一个元素并返回第一个元素值
removeLast()删除最后一个元素并返回最后一个元素值
remove(Object o)删除某一元素,返回是否成功
contains(Object o)判断是否含有某一元素
get(int index)返回指定位置的元素
getFirst()返回第一个元素
getLast()返回最后一个元素
lastIndexOf(Object o)查找指定元素最后一次出现的索引

代码演示

     	LinkedList<Integer> linkedList = new LinkedList<>();//将元素插入到末尾linkedList.add(1);linkedList.add(2);linkedList.add(3);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [1, 2, 3]//指定位置插入指定元素 位置应满足:index >= 0 && index <= size 否则会索引越界int addIndex = 2;int addValue = 7;linkedList.add(addIndex, addValue);System.out.printf("在指定位置[%d]插入元素[%d]后,链表中元素为: %s", addIndex, addValue, linkedList);  //在指定位置[2]插入元素[7]后,链表中元素为: [1, 2, 7, 3]System.out.println();//将另一个集合中的数据插入linkedList.addAll(Arrays.asList(10, 11));System.out.println("插入集合后,链表中元素为:" + linkedList);   //插入集合后,链表中元素为:[1, 2, 7, 3, 10, 11]//在头部插入元素linkedList.addFirst(20);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [20, 1, 2, 7, 3, 10, 11]//在尾部插入元素linkedList.addLast(6);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [20, 1, 2, 7, 3, 10, 11, 6]//判断是否包含if (linkedList.contains(20)) {System.out.println("链表中存在值20");     //链表中存在值20} else {System.out.println("链表中不存在值20");}//检索但不删除链表的头部System.out.println("链表的头部位置的数为: " + linkedList.element()); //链表的头部位置的数为: 20//获取某个索引上的数System.out.println("链表上索引位置为6的数为: " + linkedList.get(6)); //链表上索引位置为6的数为: 11//链表上头部元素System.out.println("链表上第一个元素为: " + linkedList.getFirst());  //链表上第一个元素为: 20//返回此列表中的最后一个元素System.out.println("链表上最后一个元素为: " + linkedList.getLast());  //链表上最后一个元素为: 6//返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。System.out.println("元素6在链表上第一次出现的位置为: " + linkedList.indexOf(6)); //元素6在链表上第一次出现的位置为: 7System.out.println("元素60在链表上第一次出现的位置为: " + linkedList.indexOf(60));  //元素60在链表上第一次出现的位置为: -1//返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。System.out.println("元素20在链表上最后一次出现的位置为: " + linkedList.lastIndexOf(20));  //元素20在链表上最后一次出现的位置为: 0//将指定的元素添加为此列表的尾部(最后一个元素)linkedList.offer(21);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [20, 1, 2, 7, 3, 10, 11, 6, 21]//在此列表的前面插入指定的元素linkedList.offerFirst(0);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [0, 20, 1, 2, 7, 3, 10, 11, 6, 21]//在该列表的末尾插入指定的元素linkedList.offerLast(100);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [0, 20, 1, 2, 7, 3, 10, 11, 6, 21, 100]//检索但不删除此列表的头(第一个元素)Integer peekValue = linkedList.peek();System.out.println("peek结果--链表的第一个元素为:" + peekValue);   //peek结果--链表的第一个元素为:0//检索但不删除此列表的第一个元素,如果此列表为空,则返回 null 。Integer peekFirstValue = linkedList.peekFirst();System.out.println("peekFirst结果--链表的第一个元素为: " + peekFirstValue); //peekFirst结果--链表的第一个元素为: 0//检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null 。Integer peekLastValue = linkedList.peekLast();System.out.println("peekLast结果--链表的最后一个元素为: " + peekLastValue); //peekLast结果--链表的最后一个元素为: 100//检索并删除此列表的头(第一个元素)Integer pollValue = linkedList.poll();System.out.printf("链表删除头部元素[%d]后,剩余的元素为[%s]", pollValue, linkedList);   //链表删除头部元素[0]后,剩余的元素为[[20, 1, 2, 7, 3, 10, 11, 6, 21, 100]]System.out.println();//检索并删除此列表的第一个元素,如果此列表为空,则返回 nullInteger pollFirstValue = linkedList.pollFirst();System.out.printf("链表删除头部元素[%d]后,剩余的元素为[%s]", pollFirstValue, linkedList);  //链表删除头部元素[20]后,剩余的元素为[[1, 2, 7, 3, 10, 11, 6, 21, 100]]System.out.println();//检索并删除此列表的最后一个元素,如果此列表为空,则返回 nullInteger pollLastValue = linkedList.pollLast();System.out.printf("链表删除尾部元素[%d]后,剩余的元素为[%s]", pollLastValue, linkedList);   //链表删除尾部元素[100]后,剩余的元素为[[1, 2, 7, 3, 10, 11, 6, 21]]System.out.println();//从此列表表示的堆栈中弹出一个元素。Integer popValue = linkedList.pop();System.out.printf("弹出元素[%d]后,剩余的元素为[%s]", popValue, linkedList);    //弹出元素[1]后,剩余的元素为[[2, 7, 3, 10, 11, 6, 21]]System.out.println();int pushValue = 101;//将元素推送到由此列表表示的堆栈上。linkedList.push(pushValue);System.out.printf("给栈中压入元素[%d]后,剩余的元素为[%s]", pushValue, linkedList);    //给栈中压入元素[101]后,剩余的元素为[[101, 2, 7, 3, 10, 11, 6, 21]]System.out.println();//检索并删除此列表的头(第一个元素)Integer removeValue = linkedList.remove();System.out.printf("删除链表中元素[%d]后,剩余的元素为[%s]", removeValue, linkedList);  //删除链表中元素[101]后,剩余的元素为[[2, 7, 3, 10, 11, 6, 21]]System.out.println();Integer removeIndexValue = linkedList.remove(2);System.out.printf("移除索引2处元素[%d]后,剩余的元素为[%s]", removeIndexValue , linkedList); //移除索引2处元素[101]后,剩余的元素为[[2, 7, 10, 11, 6, 21]]System.out.println();//从此列表中删除并返回第一个元素。Integer removeFirstValue = linkedList.removeFirst();System.out.printf("移除头部元素[%d]后,剩余的元素为:%s", removeFirstValue, linkedList);	移除头部元素[2]后,剩余的元素为:[7, 10, 11, 6, 21]System.out.println();//从此列表中删除并返回第一个元素。Integer removeLastValue = linkedList.removeLast();System.out.printf("移除头部元素[%d]后,剩余的元素为:%s", removeLastValue, linkedList);	//移除尾部元素[21]后,剩余的元素为:[7, 10, 11, 6]System.out.println();int size = linkedList.size();System.out.println("链表中的元素有:" + linkedList);    //链表中的元素有:[7, 10, 11, 6]System.out.println("链表中的大小为: " + size); //链表中的大小为: 4//以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组Integer[] array = linkedList.toArray(new Integer[0]);System.out.println("linkedList转换为数组:" + Arrays.toString(array));    //linkedList转换为数组:[7, 10, 11, 6]//清空链表linkedList.clear();System.out.println("链表清空后,里面的元素为:" + linkedList);   //链表清空后,里面的元素为:[]

HashSet的使用

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。

HashSet 允许有 null 值。

HashSet 是无序的,即不会记录插入的顺序。

HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。

HashSet 实现了 Set 接口。

构造方法

构造器 描述 HashSet()

构造一个新的空集; 支持HashMap实例具有默认初始容量(16)和加载因子(0.75)。

HashSet​(int initialCapacity)

构造一个新的空集; 支持HashMap实例具有指定的初始容量和默认加载因子(0.75)。

HashSet​(int initialCapacity, float loadFactor)

构造一个新的空集; 支持HashMap实例具有指定的初始容量和指定的加载因子。

HashSet​(Collection<? extends E> c)

构造一个包含指定集合中元素的新集合。

 常用方法API

变量和类型方法描述
booleanadd​(E e)

如果指定的元素尚不存在,则将其添加到此集合中。

voidclear()

从该集中删除所有元素。

Objectclone()

返回此 HashSet实例的浅表副本:未克隆元素本身。

booleancontains​(Object o)

如果此set包含指定的元素,则返回 true 。

booleanisEmpty()

如果此集合不包含任何元素,则返回 true 。

Iterator<E>iterator()

返回此set中元素的迭代器。

booleanremove​(Object o)

如果存在,则从该集合中移除指定的元素。

intsize()

返回此集合中的元素数(基数)。

Spliterator<E>spliterator()

在此集合中的元素上创建late-binding失败快速 Spliterator 。

  详细介绍地址:HashSet

TreeSet的使用

TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口。
TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。
TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
TreeSet 实现了Cloneable接口,意味着它能被克隆。
TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。
TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。
另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。 

构造方法

构造器 描述 TreeSet()

构造一个新的空树集,根据其元素的自然顺序进行排序。

TreeSet​(Collection<? extends E> c)

构造一个新的树集,其中包含指定集合中的元素,并根据其元素的 自然顺序进行排序 。

TreeSet​(Comparator<? super E> comparator)

构造一个新的空树集,根据指定的比较器进行排序。

TreeSet​(SortedSet<E> s)

构造一个包含相同元素并使用与指定有序集相同排序的新树集。

常用方法API

变量和类型方法描述
booleanadd​(E e)

如果指定的元素尚不存在,则将其添加到此集合中。

booleanaddAll​(Collection<? extends E> c)

将指定集合中的所有元素添加到此集合中。

Eceiling​(E e)

返回此set中大于或等于给定元素的 null元素,如果没有这样的元素,则 null 。

voidclear()

从该集中删除所有元素。

Objectclone()

返回此 TreeSet实例的浅表副本。

booleancontains​(Object o)

如果此set包含指定的元素,则返回 true 。

Iterator<E>descendingIterator()

以降序返回此集合中元素的迭代器。

NavigableSet<E>descendingSet()

返回此set中包含的元素的逆序视图。

Efirst()

返回此集合中当前的第一个(最低)元素。

Efloor​(E e)

返回此set中小于或等于给定元素的最大元素,如果没有这样的元素,则 null 。

SortedSet<E>headSet​(E toElement)

返回此set的部分视图,其元素严格小于 toElement 。

NavigableSet<E>headSet​(E toElement, boolean inclusive)

返回此set的部分视图,其元素小于(或等于,如果 inclusive为true) toElement 。

Ehigher​(E e)

返回此集合中的最小元素严格大于给定元素,如果没有这样的元素,则 null 。

booleanisEmpty()

如果此集合不包含任何元素,则返回 true 。

Iterator<E>iterator()

以升序返回此集合中元素的迭代器。

Elast()

返回此集合中当前的最后一个(最高)元素。

Elower​(E e)

返回此集合中的最大元素严格小于给定元素,如果没有这样的元素,则 null 。

EpollFirst()

检索并删除第一个(最低)元素,如果此组为空,则返回 null 。

EpollLast()

检索并删除最后一个(最高)元素,如果此集合为空,则返回 null 。

booleanremove​(Object o)

如果存在,则从该集合中移除指定的元素。

intsize()

返回此集合中的元素数(基数)。

Spliterator<E>spliterator()

在此集合中的元素上创建late-binding故障快速 Spliterator 。

NavigableSet<E>subSet​(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)

返回此set的部分视图,其元素范围为 fromElement到 toElement 。

SortedSet<E>subSet​(E fromElement, E toElement)

返回此set的部分视图,其元素范围从 fromElement (含)到 toElement (独占)。

SortedSet<E>tailSet​(E fromElement)

返回此set的部分视图,其元素大于或等于 fromElement 。

NavigableSet<E>tailSet​(E fromElement, boolean inclusive)

返回此set的部分视图,其元素大于(或等于,如果 inclusive为true) fromElement 。

 详细介绍地址:TreeSet

HashMap的使用

基于哈希表的Map接口的实现。 此实现提供了所有可选的映射操作,并允许null值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的并且允许空值。)

该实现为基本操作( getput )提供了恒定时间性能,假设散列函数在桶之间正确地分散元素。 对集合视图的迭代需要与HashMap实例的“容量”(桶数)加上其大小(键值映射的数量)成比例的时间。 因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子太低)非常重要。

HashMap的实例有两个影响其性能的参数: 初始容量负载因子 。 容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 加载因子是在自动增加容量之前允许哈希表获取的完整程度的度量。 当哈希表中的条目数超过加载因子和当前容量的乘积时,哈希表将被重新哈希(即,重建内部数据结构),以便哈希表具有大约两倍的桶数。

作为一般规则,默认加载因子(.75)在时间和空间成本之间提供了良好的折衷。 较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括getput )。 在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以便最小化重新散列操作的数量。 如果初始容量大于最大条目数除以加载因子,则不会发生重新加载操作。

如果要将多个映射存储在HashMap实例中,则使用足够大的容量创建映射将允许映射更有效地存储,而不是根据需要执行自动重新散列来扩展表。 请注意,使用具有相同hashCode()许多键是减慢任何哈希表性能的可靠方法。 为了改善影响,当键为Comparable时 ,此类可以使用键之间的比较顺序来帮助打破关系。

请注意,此实现不同步。 如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。)这通常通过同步自然封装映射的某个对象来完成。 。 如果不存在此类对象,则应使用Collections.synchronizedMap方法“包装”地图。 这最好在创建时完成,以防止意外地不同步访问地图:

  Map m = Collections.synchronizedMap(new HashMap(...)); 

所有这个类的“集合视图方法”返回的迭代器都是快速失败的 :如果在创建迭代器之后的任何时候对映射进行结构修改,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器以尽力而为的方式抛出ConcurrentModificationException 。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。

此类是Java Collections Framework的成员。

构造方法

构造器 描述 HashMap()

使用默认初始容量(16)和默认加载因子(0.75)构造一个空 HashMap 。

HashMap​(int initialCapacity)

使用指定的初始容量和默认加载因子(0.75)构造一个空 HashMap 。

HashMap​(int initialCapacity, float loadFactor)

使用指定的初始容量和加载因子构造一个空 HashMap 。

HashMap​(Map<? extends K,​? extends V> m)

构造一个新的 HashMap ,其映射与指定的 Map相同。

 常用方法API

变量和类型方法描述
voidclear()

从此映射中删除所有映射。

Objectclone()

返回此 HashMap实例的浅表副本:未克隆键和值本身。

Vcompute​(K key, BiFunction<? super K,​? super V,​? extends V> remappingFunction)

尝试计算指定键及其当前映射值的映射(如果没有当前映射, null )。

VcomputeIfAbsent​(K key, Function<? super K,​? extends V> mappingFunction)

如果指定的键尚未与值关联(或映射到 null ),则尝试使用给定的映射函数计算其值并将其输入此映射,除非 null 。

VcomputeIfPresent​(K key, BiFunction<? super K,​? super V,​? extends V> remappingFunction)

如果指定键的值存在且为非null,则尝试在给定键及其当前映射值的情况下计算新映射。

booleancontainsKey​(Object key)

如果此映射包含指定键的映射,则返回 true 。

booleancontainsValue​(Object value)

如果此映射将一个或多个键映射到指定值,则返回 true 。

Set<Map.Entry<K,​V>>entrySet()

返回此映射中包含的映射的Set视图。

Vget​(Object key)

返回指定键映射到的值,如果此映射不包含键的映射,则返回 null 。

booleanisEmpty()

如果此映射不包含键 - 值映射,则返回 true 。

Set<K>keySet()

返回此映射中包含的键的Set视图。

Vmerge​(K key, V value, BiFunction<? super V,​? super V,​? extends V> remappingFunction)

如果指定的键尚未与值关联或与null关联,则将其与给定的非空值关联。

Vput​(K key, V value)

将指定的值与此映射中的指定键相关联。

voidputAll​(Map<? extends K,​? extends V> m)

将指定映射中的所有映射复制到此映射。

Vremove​(Object key)

从此映射中删除指定键的映射(如果存在)。

intsize()

返回此映射中键 - 值映射的数量。

Collection<V>values()

返回此映射中包含的值的Collection视图。

详细介绍地址:HashMap

TreeMap的使用

基于红黑树的NavigableMap实现。 该地图是根据排序natural ordering其密钥,或通过Comparator在地图创建时提供,这取决于所使用的构造方法。

此实现提供了保证的log(n)时间成本containsKey , get , putremove操作。 算法是对Cormen,Leiserson和Rivest的算法导论中的算法的改编。

请注意,如果此有序映射要正确实现Map接口,则树映射维护的排序(如任何已排序的映射,以及是否提供显式比较器)必须equals一致 。 (有关与equals一致的精确定义,请参阅ComparableComparator )这是因为Map接口是根据equals操作定义的,但是有序映射使用其compareTo (或compare )方法执行所有键比较,因此从排序映射的角度来看,通过此方法被视为相等的键是相等的。 即使排序与equals不一致,也可以很好地定义有序映射的行为。 它只是没有遵守Map接口的一般合同。

请注意,此实现不同步。 如果多个线程同时访问映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅仅更改与现有键关联的值不是结构修改。)这通常通过在自然封装映射的某个对象上进行同步来实现。 如果不存在此类对象,则应使用Collections.synchronizedSortedMap方法“包装”地图。 这最好在创建时完成,以防止意外地不同步访问地图:

  SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...)); 

由此类的所有“集合视图方法”返回的集合的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时候对映射进行结构修改,除非通过迭代器自己的remove方法,迭代器将抛出一个ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器会尽最大努力抛出ConcurrentModificationException 。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。

Map.Entry的方法返回的所有Map.Entry对及其视图表示生成时映射的快照。 他们支持Entry.setValue方法。 (但请注意,可以使用put更改关联映射中的映射。)

此类是Java Collections Framework的成员。

构造方法

构造器 描述 TreeMap()

使用其键的自然顺序构造一个新的空树图。

TreeMap​(Comparator<? super K> comparator)

构造一个新的空树图,根据给定的比较器排序。

TreeMap​(Map<? extends K,​? extends V> m)

构造一个新的树映射,其中包含与给定映射相同的映射,根据其键的 自然顺序排序 。

TreeMap​(SortedMap<K,​? extends V> m)

构造一个包含相同映射的新树映射,并使用与指定有序映射相同的顺序。

  常用方法API

变量和类型方法描述
Map.Entry<K,​V>ceilingEntry​(K key)

返回与大于或等于给定键的最小键关联的键 - 值映射,如果没有此键,则 null 。

KceilingKey​(K key)

返回大于或等于给定键的 null键,如果没有这样的键,则 null 。

voidclear()

从此映射中删除所有映射。

Objectclone()

返回此 TreeMap实例的浅表副本。

booleancontainsKey​(Object key)

如果此映射包含指定键的映射,则返回 true 。

booleancontainsValue​(Object value)

如果此映射将一个或多个键映射到指定值,则返回 true 。

NavigableSet<K>descendingKeySet()

返回此映射中包含的键的反向顺序NavigableSet视图。

NavigableMap<K,​V>descendingMap()

返回此映射中包含的映射的逆序视图。

Set<Map.Entry<K,​V>>entrySet()

返回此映射中包含的映射的Set视图。

Map.Entry<K,​V>firstEntry()

返回与此映射中的最小键关联的键 - 值映射,如果映射为空,则 null 。

KfirstKey()

返回此映射中当前的第一个(最低)键。

Map.Entry<K,​V>floorEntry​(K key)

返回与小于或等于给定键的最大键关联的键 - 值映射,如果没有此键,则 null 。

KfloorKey​(K key)

返回小于或等于给定键的最大键,如果没有这样的键,则 null 。

Vget​(Object key)

返回指定键映射到的值,如果此映射不包含键的映射,则返回 null 。

SortedMap<K,​V>headMap​(K toKey)

返回此映射的部分视图,其键严格小于 toKey 。

NavigableMap<K,​V>headMap​(K toKey, boolean inclusive)

返回此映射的部分视图,其键小于(或等于,如果 inclusive为真) toKey 。

Map.Entry<K,​V>higherEntry​(K key)

返回与严格大于给定键的最小键关联的键 - 值映射,如果没有此键,则 null 。

KhigherKey​(K key)

返回严格大于给定键的最小键,如果没有这样的键,则返回 null 。

Set<K>keySet()

返回此映射中包含的键的Set视图。

Map.Entry<K,​V>lastEntry()

返回与此映射中的最大键关联的键 - 值映射,如果映射为空,则 null 。

KlastKey()

返回此映射中当前的最后一个(最高)键。

Map.Entry<K,​V>lowerEntry​(K key)

返回与严格小于给定键的最大键相关联的键 - 值映射,如果没有这样的键,则 null 。

KlowerKey​(K key)

返回严格小于给定键的最大键,如果没有这样键,则返回 null 。

NavigableSet<K>navigableKeySet()

返回此映射中包含的键的NavigableSet视图。

Map.Entry<K,​V>pollFirstEntry()

删除并返回与此映射中的最小键关联的键 - 值映射,如果映射为空,则 null 。

Map.Entry<K,​V>pollLastEntry()

删除并返回与此映射中的最大键关联的键 - 值映射,如果映射为空,则 null 。

Vput​(K key, V value)

将指定的值与此映射中的指定键相关联。

voidputAll​(Map<? extends K,​? extends V> map)

将指定映射中的所有映射复制到此映射。

Vremove​(Object key)

如果存在,则从此TreeMap中删除此键的映射。

intsize()

返回此映射中键 - 值映射的数量。

NavigableMap<K,​V>subMap​(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)

返回此映射部分的视图,其键范围为 fromKey至 toKey 。

SortedMap<K,​V>subMap​(K fromKey, K toKey)

返回此映射部分的视图,其键的范围从 fromKey (包括 toKey )到 toKey (独占)。

SortedMap<K,​V>tailMap​(K fromKey)

返回此映射的部分视图,其键大于或等于 fromKey 。

NavigableMap<K,​V>tailMap​(K fromKey, boolean inclusive)

返回此映射的部分视图,其键大于(或等于,如果 inclusive为真) fromKey 。

Collection<V>values()

返回此映射中包含的值的Collection视图。

详细介绍地址:TreeMap

自动拆箱和自动装箱

(1)自动装箱

Java自动将基本数据类型转换为包装类型,也就是int→Integer,实际上是调用了方法Integer.valueOf(int)。

(2)自动拆箱

Java自动将包装类型转换为基本数据类型,也就是Integer→int,实际上是调用了方法Integer.intValue()。

基本数据类型对应包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

什么是类型擦除?

Java在编译后的(.class)文件中是不包含泛型中的类型信息的,使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉,这个过程就称为类型擦除。

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();        
System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为 List<String>和 List<Integer>在 jvm 中的 Class 都是 List.class。泛型信息被擦除了。泛型类被类型擦除后,相应的类型就被替换成 Object 类型
在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

public class Erasure <T extends String>{
//  public class Erasure <T>{T object;public Erasure(T object) {this.object = object;}}public static void main(String[] args) {Erasure<String> str = new Erasure<String>();System.out.println(l1.getClass() == l2.getClass());     }
  • 此时打印结果为Erasure.class
  • 在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

注解

注解的定义和应用场景

注解的定义

注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。

应用场景

应用场景一:自定义注解+拦截器 实现登录校验

使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。

首先定义一个LoginRequired注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {}

然后写两个简单的接口,访问sourceA,sourceB资源

@RestController
public class IndexController {@GetMapping("/sourceA")public String sourceA(){return "你正在访问sourceA资源";}@GetMapping("/sourceB")public String sourceB(){return "你正在访问sourceB资源";}
}

 没添加拦截器之前成功访问

 实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

public class SourceAccessInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("进入拦截器了");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");}
}

 拦截成功如下:

在sourceB方法上添加我们的登录注解@LoginRequired:

@RestController
public class IndexController {@GetMapping("/sourceA")public String sourceA(){return "你正在访问sourceA资源";}@LoginRequired@GetMapping("/sourceB")public String sourceB(){return "你正在访问sourceB资源";}
}

 简单实现登录拦截逻辑

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("进入拦截器了");// 反射获取方法上的LoginRequred注解HandlerMethod handlerMethod = (HandlerMethod)handler;LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);if(loginRequired == null){return true;}// 有LoginRequired注解说明需要登录,提示用户登录response.setContentType("application/json; charset=utf-8");response.getWriter().print("你访问的资源需要登录");return false;
}

运行成功,访问sourceB时需要登录了,访问sourceA则不用登录。 

应用场景二:自定义注解+AOP 实现日志打印 

先导入切面需要的依赖包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义一个注解@MyLog

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {}

 定义一个切面类,见如下代码注释理解:

@Aspect // 1.表明这是一个切面类
@Component
public class MyLogAspect {// 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名// 切面最主要的就是切点,所有的故事都围绕切点发生// logPointCut()代表切点名称@Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")public void logPointCut(){};// 3. 环绕通知@Around("logPointCut()")public void logAround(ProceedingJoinPoint joinPoint){// 获取方法名称String methodName = joinPoint.getSignature().getName();// 获取入参Object[] param = joinPoint.getArgs();StringBuilder sb = new StringBuilder();for(Object o : param){sb.append(o + "; ");}System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());// 继续执行方法try {joinPoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println(methodName + "方法执行结束");}
}

在步骤二中的IndexController写一个sourceC进行测试,加上我们的自定义注解:

@MyLog
@GetMapping("/sourceC/{source_name}")
public String sourceC(@PathVariable("source_name") String sourceName){return "你正在访问sourceC资源";
}

 启动springboot web项目,输入访问地址

注解的本质和原理

注解本质是一个继承了Annotation接口的特殊接口,当你用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,如果允许的话就会将注解信息写入元素的属性表。当通过反射获取注解时,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到memberValues这个map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类对象,并初始化好处理器。通过代理对象调用注解的方法时,最终调用AnnotationInvocationHandler 的invoke方法。该方法会从memberValues 这个Map 中索引出对应的值并设置进使用这个注解的元素中去(memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值,memberValues 的来源是Java 常量池),一句话概括就是,通过方法名返回注解属性值。

扩展

AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler, 这个类的设计也非常有意思。

这里有一个 memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。

而这个 invoke 方法就很有意思了,大家注意看,我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来。

var2 指向被调用的方法实例,而这里首先用变量 var4 获取该方法的简明名称,接着 switch 结构判断当前的调用方法是谁,如果是 Annotation 中的四大方法,将 var7 赋上特定的值。如果当前调用的方法是 toString,equals,hashCode,annotationType 的话,AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。

那么假如 var7 没有匹配上这四种方法,说明当前的方法调用的是自定义注解字节声明的方法,这种情况下,将从我们的注解 map 中获取这个注解属性对应的值。一句话概括就是,通过方法名返回注解属性值。

好文参考:注解机制及其原理什么是注解注解的使用注解的原理 - 腾讯云开发者社区-腾讯云

元注解和预置注解

元注解

『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

这是我们 @Override 注解的定义,你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息。

JAVA 中有以下几个『元注解』:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解

其中,@Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。

@Target 的定义如下:

我们可以通过以下的方式来为这个 value 传值:

@Target(value = {ElementType.FIELD})

被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

@Retention 用于指明当前注解的生命周期,它的基本定义如下:

同样的,它也有一个 value 属性:

@Retention(value = RetentionPolicy.RUNTIME

这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:

  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。

剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

 预置注解

除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@Override 注解想必是大家很熟悉的了,它的定义如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。

所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。

@Deprecated 的基本定义如下:

依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。

当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。

@SuppressWarnings 主要用来压制 java 的警告,它的基本定义如下:

它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:

public static void main(String[] args) {Date date = new Date(2018, 7, 11);
}

这么一段代码,程序启动时编译器会报一个警告。

Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时

而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {Date date = new Date(2018, 7, 11);
}

这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。

当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。

自定义注解的相关内容就不再赘述了,比较简单,通过类似以下的语法即可自定义一个注解。

public @interface InnotationName{}

当然,自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。

 好文参考:JAVA 注解的基本原理 - Single_Yam - 博客园

IO流

File类的基本操作

  • File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  • File类声明在java.io包下
  • File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
  • 后续 File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

File类的构造方法

  •     File(String filePath)
  •     File(String parentPath,String childPath)
  •     File(File parentFile,String childPath)


常用方法API

方法名介绍
public String getAbsolutePath()
获取绝对路径
public String getPath()
获取路径
public String getName()
获取名称
public String getParent()
获取上层文件目录路径。若无,返回null
public long length()
获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified()
获取最后一次的修改时间,毫秒值
public String[] list()
获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles()
获取指定目录下的所有文件或者文件目录的File数组
public boolean renameTo(File dest)
把文件重命名为指定的文件路径
public boolean isDirectory()
判断是否是文件目录
public boolean isFile()
判断是否是文件
public boolean exists()
判断是否存在
public boolean canRead()
判断是否可读
public boolean canWrite()
判断是否可写
public boolean isHidden()
判断是否隐藏
public boolean createNewFile()
创建文件。若文件存在,则不创建,返回false
public boolean mkdir()
创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs()
创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建、
public boolean delete()
删除文件或者文件夹

好文参考: File类操作_巛小文子......的博客-CSDN博客_file 相关操作

Reader字符读取

java 中的 IO 输入流不是只有 InputStream 还有按字符输入的 Reader。

 和 InputStream 一样,Reader 也是所有字符输入流的超类。主要的方法是:public int read() throws IOException,read() 读取字符流中的下一个字符,返回 0-65535 的 int 类型数值, 返回 -1 表示已经读取结束。

FileReader

FileReader 打开一个文件并获取到文件的字符流。FileReader 用于读取文件中的内容。

private void fileReaderDemo() throws Exception {Reader reader = new FileReader("D:\\readerDemo.txt");int n;while ((n = reader.read()) != -1) {System.out.print((char)n);}reader.close();
}

Reader 实现了 Closeable 接口,可以用 try(Reader reader = new FileReader("D:\\readerDemo.txt")) {} 的方式关闭掉资源。

InputStreamReader

InputStreamReader 就是将 InputStream 读取的字节流装换为 Reader 的字符流。可以把任意的 InputStream 转换为 Reader,FileReader 就继承自 InputStreamReader。在创建 InputStreamReader 实例对象的时候可以指定字符集,以防止乱码。

private void inputStreamReaderDemo() throws Exception {InputStream inputStream = new FileInputStream("D:\\readerDemo.txt");try(Reader reader = new InputStreamReader(inputStream, "utf-8")) {int n;while ((n = reader.read()) != -1) {System.out.print((char)n);}}
}

StringReader 和 CharArrayReader

FileReader 是将文件作为一个读取源,StringReader 将 string 字符串作为一个读取源。

private void stringReaderDemo() throws Exception {try(Reader reader =  new StringReader("这是测试代码")) {char[] buffer = new char[1024];while ((reader.read(buffer)) != -1) {System.out.print(buffer);}}
}

reader.read(char[] buffer) 是 reader 读取字符流的重载方法,将内容不在是一个 char 一个 char 的输出,而是将内容读取到缓冲区 buffer 后一次性输出。

CharArrayReader 和 StringReader 几乎一样,调用方法变成了 try(Reader reader = new CharArrayReader("这是测试代码".toCharArray()))

BufferedReader

提供通用的缓冲方式读取文本并且提供了 readLine() 读取了一个文本行。从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。

private void bufferedReaderDemo() throws Exception {try(BufferedReader reader =  new BufferedReader(new FileReader("D:\\readerDemo.txt"))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}}
}

总结

介绍了几种常用 Reader 输入流的使用方式。FileReader 用于文件读取,BufferedReader 自带缓冲区读取效率高,StringReader 和 CharArrayReader 可以读取字符串源,InputStreamReader 将 InputStream 转为 Reader。

Writer字符输出

Writer是输出字符流的⽗父类,它是⼀一个抽象类

public void write(int c) throws IOException

讲解:直接将int型数据作为参数的话,是不不会写⼊入数字的,⽽而是现将数字按照ascll码表转换为相应的字符,然后写⼊入

public void write(String str) throws IOException
讲解:要想实现数字和中⽂文的写⼊入,必须要⽤用String为参数的Write

public abstract void write(char cbuf[], int off, int len) throws IOException;
讲解:将cbuf字符数组的⼀一部分写⼊入到流中,但能不能写len个字符取决于cbuf中是否有那么多
void flush() throws IOException
讲解:write是写到缓冲区中,可以认为是内存中,当缓冲区满时系统会⾃自动将缓冲区的内容写⼊入⽂文件,但是⼀一般还有⼀一部分有可能会留留在内存这个缓冲区中, 所以需要调⽤用flush空缓冲区数据。
void close() throws IOException
讲解:关闭输入流并释放与该流关联的系统资源

FileWriter用来写出字符文件的实现类 

 public FileWriter(String fileName) throws IOException
讲解:如果⽂文件不不存在,这会⾃自动创建。如果⽂文件存在,则会覆盖
public FileWriter(File file) throws IOException
讲解:如果⽂文件不不存在,这会⾃自动创建。如果⽂文件存在,则会覆盖
public FileWriter(String fileName, boolean append) throws
IOException
讲解:加⼊入true参数,会实现对⽂文件的续写,使⽤用false则会实现对⽂文件的覆盖
public FileWriter(File file, boolean append) throws IOException
讲解:加⼊入true参数,会实现对⽂文件的续写,使⽤用false则会实现对⽂文件的覆盖

 实战

package chapter12;import javax.imageio.IIOException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class WriterTest {public static void main(String [] args)throws Exception {test2();}public static void test2()throws Exception {String dir="C:\\Users\\联想\\Desktop\\test\\2.txt";Writer writer = new FileWriter(dir,true);writer.write(23567);writer.write(28404);writer.write(35838);writer.write(22530);writer.write("23567");writer.flush();writer.close();}public static void test1()throws Exception {String dir="C:\\Users\\联想\\Desktop\\test\\2.txt";Writer writer = new FileWriter(dir);writer.write(23567);writer.write(28404);writer.write(35838);writer.write(22530);writer.write("23567");writer.flush();writer.close();}
}

 好文参考:Java进阶核⼼之Reader、Writer字符流_小白~一看就懂的博客-CSDN博客_java输入输出之writer类之字符数据输出

实习

基础语法

基本数据类型(熟悉)

Java有哪些数据类型
定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

boolean类型占多少个字节?

  • boolean类型被编译为int类型,等于是说JVM里占用字节和int完全一样,int是4个字节,于是boolean也是4字节
  • boolean数组在Oracle的JVM中,编码为byte数组,每个boolean元素占用8位=1字节(待考证)boolean数组在非Oracle JVM里面,占用字节数也许不是1字节


jdk8的官方文档解释

虽然Java虚拟机定义了一个boolean类型,但它只为它提供了非常有限的支持。没有Java虚拟机指令专门用于对boolean值的操作。相反,Java编程语言中对boolean值进行操作的表达式被编译为使用Java虚拟机int数据类型的值。

Java虚拟机直接支持boolean数组。它的newarray指令可以创建boolean数组。使用byte数组指令baload和bastore访问和修改类型为boolean的数组。

在Oracle的Java虚拟机实现中,Java编程语言中的boolean数组被编码为Java虚拟机byte数组,每个布尔元素使用8位。

Java虚拟机使用1表示boolean数组组件的true,0表示false。其中Java编程语言布尔值由编译器映射到Java虚拟机类型int的值,编译器必须使用相同的编码。

变量类型(熟悉)

访问修饰符(熟悉)

1、访问修饰符定义
Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

2、访问修饰符分类
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

protected : 对同一包内的类和不同包的所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

public : 对所有类可见。使用对象:类、接口、变量、方法

3、访问修饰符图

 面向对象

抽象

抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

封装

封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

专业描述:【隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。】

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

关于继承如下 3 点请记住:

1、子类拥有父类非 private 的属性和方法。

2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

3、子类可以用自己的方式实现父类的方法(重写)。

多态

所谓多态就是指一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

专业描述:【父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。】

在Java中有两种形式可以实现多态:继承(子类对父类同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

方法重载(overload)实现的是编译时的多态性(也称为前绑定)

方法重写(override)实现的是运行时的多态性(也称为后绑定)。


重载(Overload)和重写(Override)的区别?

  • 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
  • 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
  • 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表和返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
  • 重载对返回类型没有特殊的要求,不能根据返回类型进行区分。重写要求和父类被重写的方法有相同的返回类型

内部类的分类有哪些?


内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。

1、静态内部类

定义在类内部的静态类,就是静态内部类。

public class Outer {private static int radius =1;//静态内部类
static class StaticInner{ 
public void visit(){System.out.println("visit outer static variable:"+ radius);}}
}

静态内部类可以访问外部类所有的静态变量,但是不可访问外部类的非静态变量;

静态内部类的创建方式,new 外部类.静态内部类(),如下:

Outer.StaticInner inner =new Outer.StaticInner();inner.visit();

2、成员内部类

定义在类内部,成员位置上的非静态类,就是成员内部类。

public class Outer {
private static int radius =1;
privateint count =2;
class Inner{
public void visit(){    
System.out.println("visit outer static  variable:"+ radius);    
System.out.println("visit outer variable:"+ count);}}
}
成

成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。

成员内部类依赖于外部类的实例,它的创建方式外部类实例.new 内部类(),如下:

Outer outer =new Outer();Outer.Inner inner = outer.new Inner();inner.visit();

3、局部内部类

定义在方法中的内部类,就是局部内部类。

public class Outer{private int out_a =1;private static int STATIC_b =2;public void test FunctionClass(){int inner_c=3;class Inner{private void fun(){        System.out.println(out_a);    System.out.println(STATIC_b); System.out.println(inner_c);}}    Inner  inner =new Inner();    //直接在方法里用new创建局部内部类inner.fun();}public static void test StaticFunctionClass(){int d=3;class Inner{private void fun(){//System.out.println(out_a); //编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量System.out.println(STATIC_b);    System.out.println(d);}}    Inner  inner =new Inner();    //直接在方法里用new创建局部内部类inner.fun();}
} 

定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和静态方法 。

局部内部类的创建方式 ,在对应方法内 new 内部类(), 如下 :

public static void testStaticFunctionClass(){class Inner{} Inner inner =new Inner();
}

4、匿名内部类

匿名内部类就是没有名字的内部类

匿名内部类举例

 红色框画出来的就是一个匿名内部类同时重写了父类Animals的eat方法,并且调用了这个匿名内部类的eat方法

如何调用匿名内部类中的方法?

1、匿名内部类中只有一个方法的情况

2、匿名内部类有多个方法

第一种方式

 第二种方式

 如果想调用匿名内部类自己特有的方法的时候呢?该如何调用呢?

  

匿名内部类可以有自己特有的方法,但是前提条件是这个匿名内部类只有这一个方法(不重写父类或者接口的方法)。如果有多个方法的时候,他只能继承父类的方法以及重写这个方法或实现接口,绝不可能在在多个方法的情况下,调用自身特有的方法,但是这个特有的方法可以存在,但无法调用

匿名内部类是实现接口

 匿名内部类存在的前提是要有继承或者实现关系的,但是并没有看到extends和implements关键字,这是怎么回事呢?

答:很简单,匿名内部类没有连类名都没有,使用关键字就更无从说起了。这些由jvm搞定了。

匿名内部类的特点

除了没有名字,匿名内部类还有以下特点:

1、匿名内部类必须继承一个抽象类或者实现一个接口。

2、匿名内部类不能定义任何静态成员和静态方法。

3、匿名内部类访问局部变量的时候,必须把局部变量声明为 final。

4、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

匿名内部类创建方式:

new 类/接口{

//匿名内部类实现部分

}

什么是枚举?

枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。

提示:如果没有显式地声明基础类型的枚举,那么意味着它所对应的基础类型是 int。

下面代码定义了一个表示性别的枚举类型 SexEnum 和一个表示颜色的枚举类型 Color。

public enum SexEnum {male,female;
}
public enum Color {RED,BLUE,GREEN,BLACK;
}

之后便可以通过枚举类型名直接引用常量,如 SexEnum.male、Color.RED。

枚举类

Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public、 static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。

所有枚举实例都可以调用 Enum 类的方法,常用方法如表 1 所示。

 1、通过调用枚举类型实例的 values( ) 方法可以将枚举的所有成员以数组形式返回,也可以通过该方法获取枚举类型的成员。

下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 values() 方法输出这些成员。

enum Signal {// 定义一个枚举类型GREEN,YELLOW,RED;
}
public static void main(String[] args) {for(int i = 0;i < Signal.values().length;i++) {System.out.println("枚举成员:"+Signal.values()[i]);}
}

输出结果如下:

枚举成员:GREEN
枚举成员:YELLOW
枚举成员:RED

2、创建一个示例,调用valueOf() 方法获取枚举的一个成员,再调用 compareTo() 方法进行比较,并输出结果。具体实现代码如下:

public class TestEnum {public enum Sex {// 定义一个枚举male,female;}public static void main(String[] args) {compare(Sex.valueOf("male"));    // 比较}public static void compare(Sex s) {for(int i = 0;i < Sex.values().length;i++) {System.out.println(s + "与" + Sex.values()[i] + "的比较结果是:" + spareTo(Sex.values()[i]));}}
}

上述代码中使用 Sex.valueOf("male") 取出枚举成员 male 对应的值,再将该值与其他枚举成员进行比较。最终输出结果如下:

male与male的比较结果是:0
male与female的比较结果是:-1

3、通过调用枚举类型实例的ordinal() 方法可以获取一个成员在枚举中的索引位置。下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 ordinal() 方法输出成员及对应索引位置。

public class TestEnum1 {enum Signal {// 定义一个枚举类型GREEN,YELLOW,RED;}public static void main(String[] args) {for(int i = 0;i < Signal.values().length;i++) {System.out.println("索引" + Signal.values()[i].ordinal()+",值:" + Signal.values()[i]);}}
}

输出结果如下:

索引0,值:GREEN
索引1,值:YELLOW
索引2,值:RED

集合与泛型

ArrayList的使用

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

ArrayList是实现List接口的,底层采用数组实现。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

ArrayList集合的特点: (多用于查询)

1)集合数据存储的结构是数组结构。
2)元素增删慢,查找快

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

 

Java新手教程之ArrayList的基本使用_java_脚本之家

关于ArrayList的使用_菜鸟是鸟菜的博客-CSDN博客_arraylist怎么用

ArrayList常用方法总结_涟涟涟涟的博客-CSDN博客_arraylist方法

LinkedList的使用

LinkedList 实现了Queue接口,能当作队列使用。
LinkedList 实现了List 接口,能对它进行列表操作。
LinkedList 实现了Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,能克隆。
LinkedList 实现了java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList集合的特点: (多用于增删)

  1. 底层是一个链的结构:查询慢,增删快
  2. 里边包含了大量操作首尾元素的方法

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
常用API描述

add()

添加元素

get()

获取元素

addFirst(E e)

元素添加到头部

addLast(E e)

元素添加到尾部

clear()

清空链表

remove(int index)

删除指定位置的元素

indexOf()

查找指定元素从前往后第一次出现的索引

clone()

克隆列表

size()

返回链表元素个数

add(int index, E element)向指定位置插入元素
addAll(Collection<? extends E> c)将一个集合的所有元素添加到链表后面
addAll(int index, Collection<? extends E> c)将一个集合的所有元素添加到链表的指定位置后面
removeFirst()删除第一个元素并返回第一个元素值
removeLast()删除最后一个元素并返回最后一个元素值
remove(Object o)删除某一元素,返回是否成功
contains(Object o)判断是否含有某一元素
get(int index)返回指定位置的元素
getFirst()返回第一个元素
getLast()返回最后一个元素
lastIndexOf(Object o)查找指定元素最后一次出现的索引

代码演示

     	LinkedList<Integer> linkedList = new LinkedList<>();//将元素插入到末尾linkedList.add(1);linkedList.add(2);linkedList.add(3);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [1, 2, 3]//指定位置插入指定元素 位置应满足:index >= 0 && index <= size 否则会索引越界int addIndex = 2;int addValue = 7;linkedList.add(addIndex, addValue);System.out.printf("在指定位置[%d]插入元素[%d]后,链表中元素为: %s", addIndex, addValue, linkedList);  //在指定位置[2]插入元素[7]后,链表中元素为: [1, 2, 7, 3]System.out.println();//将另一个集合中的数据插入linkedList.addAll(Arrays.asList(10, 11));System.out.println("插入集合后,链表中元素为:" + linkedList);   //插入集合后,链表中元素为:[1, 2, 7, 3, 10, 11]//在头部插入元素linkedList.addFirst(20);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [20, 1, 2, 7, 3, 10, 11]//在尾部插入元素linkedList.addLast(6);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [20, 1, 2, 7, 3, 10, 11, 6]//判断是否包含if (linkedList.contains(20)) {System.out.println("链表中存在值20");     //链表中存在值20} else {System.out.println("链表中不存在值20");}//检索但不删除链表的头部System.out.println("链表的头部位置的数为: " + linkedList.element()); //链表的头部位置的数为: 20//获取某个索引上的数System.out.println("链表上索引位置为6的数为: " + linkedList.get(6)); //链表上索引位置为6的数为: 11//链表上头部元素System.out.println("链表上第一个元素为: " + linkedList.getFirst());  //链表上第一个元素为: 20//返回此列表中的最后一个元素System.out.println("链表上最后一个元素为: " + linkedList.getLast());  //链表上最后一个元素为: 6//返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。System.out.println("元素6在链表上第一次出现的位置为: " + linkedList.indexOf(6)); //元素6在链表上第一次出现的位置为: 7System.out.println("元素60在链表上第一次出现的位置为: " + linkedList.indexOf(60));  //元素60在链表上第一次出现的位置为: -1//返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。System.out.println("元素20在链表上最后一次出现的位置为: " + linkedList.lastIndexOf(20));  //元素20在链表上最后一次出现的位置为: 0//将指定的元素添加为此列表的尾部(最后一个元素)linkedList.offer(21);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [20, 1, 2, 7, 3, 10, 11, 6, 21]//在此列表的前面插入指定的元素linkedList.offerFirst(0);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [0, 20, 1, 2, 7, 3, 10, 11, 6, 21]//在该列表的末尾插入指定的元素linkedList.offerLast(100);System.out.println("linkedList中现有元素为:   " + linkedList);    //linkedList中现有元素为:   [0, 20, 1, 2, 7, 3, 10, 11, 6, 21, 100]//检索但不删除此列表的头(第一个元素)Integer peekValue = linkedList.peek();System.out.println("peek结果--链表的第一个元素为:" + peekValue);   //peek结果--链表的第一个元素为:0//检索但不删除此列表的第一个元素,如果此列表为空,则返回 null 。Integer peekFirstValue = linkedList.peekFirst();System.out.println("peekFirst结果--链表的第一个元素为: " + peekFirstValue); //peekFirst结果--链表的第一个元素为: 0//检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null 。Integer peekLastValue = linkedList.peekLast();System.out.println("peekLast结果--链表的最后一个元素为: " + peekLastValue); //peekLast结果--链表的最后一个元素为: 100//检索并删除此列表的头(第一个元素)Integer pollValue = linkedList.poll();System.out.printf("链表删除头部元素[%d]后,剩余的元素为[%s]", pollValue, linkedList);   //链表删除头部元素[0]后,剩余的元素为[[20, 1, 2, 7, 3, 10, 11, 6, 21, 100]]System.out.println();//检索并删除此列表的第一个元素,如果此列表为空,则返回 nullInteger pollFirstValue = linkedList.pollFirst();System.out.printf("链表删除头部元素[%d]后,剩余的元素为[%s]", pollFirstValue, linkedList);  //链表删除头部元素[20]后,剩余的元素为[[1, 2, 7, 3, 10, 11, 6, 21, 100]]System.out.println();//检索并删除此列表的最后一个元素,如果此列表为空,则返回 nullInteger pollLastValue = linkedList.pollLast();System.out.printf("链表删除尾部元素[%d]后,剩余的元素为[%s]", pollLastValue, linkedList);   //链表删除尾部元素[100]后,剩余的元素为[[1, 2, 7, 3, 10, 11, 6, 21]]System.out.println();//从此列表表示的堆栈中弹出一个元素。Integer popValue = linkedList.pop();System.out.printf("弹出元素[%d]后,剩余的元素为[%s]", popValue, linkedList);    //弹出元素[1]后,剩余的元素为[[2, 7, 3, 10, 11, 6, 21]]System.out.println();int pushValue = 101;//将元素推送到由此列表表示的堆栈上。linkedList.push(pushValue);System.out.printf("给栈中压入元素[%d]后,剩余的元素为[%s]", pushValue, linkedList);    //给栈中压入元素[101]后,剩余的元素为[[101, 2, 7, 3, 10, 11, 6, 21]]System.out.println();//检索并删除此列表的头(第一个元素)Integer removeValue = linkedList.remove();System.out.printf("删除链表中元素[%d]后,剩余的元素为[%s]", removeValue, linkedList);  //删除链表中元素[101]后,剩余的元素为[[2, 7, 3, 10, 11, 6, 21]]System.out.println();Integer removeIndexValue = linkedList.remove(2);System.out.printf("移除索引2处元素[%d]后,剩余的元素为[%s]", removeIndexValue , linkedList); //移除索引2处元素[101]后,剩余的元素为[[2, 7, 10, 11, 6, 21]]System.out.println();//从此列表中删除并返回第一个元素。Integer removeFirstValue = linkedList.removeFirst();System.out.printf("移除头部元素[%d]后,剩余的元素为:%s", removeFirstValue, linkedList);	移除头部元素[2]后,剩余的元素为:[7, 10, 11, 6, 21]System.out.println();//从此列表中删除并返回第一个元素。Integer removeLastValue = linkedList.removeLast();System.out.printf("移除头部元素[%d]后,剩余的元素为:%s", removeLastValue, linkedList);	//移除尾部元素[21]后,剩余的元素为:[7, 10, 11, 6]System.out.println();int size = linkedList.size();System.out.println("链表中的元素有:" + linkedList);    //链表中的元素有:[7, 10, 11, 6]System.out.println("链表中的大小为: " + size); //链表中的大小为: 4//以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组Integer[] array = linkedList.toArray(new Integer[0]);System.out.println("linkedList转换为数组:" + Arrays.toString(array));    //linkedList转换为数组:[7, 10, 11, 6]//清空链表linkedList.clear();System.out.println("链表清空后,里面的元素为:" + linkedList);   //链表清空后,里面的元素为:[]

HashSet的使用

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。

HashSet 允许有 null 值。

HashSet 是无序的,即不会记录插入的顺序。

HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。

HashSet 实现了 Set 接口。

构造方法

构造器 描述 HashSet()

构造一个新的空集; 支持HashMap实例具有默认初始容量(16)和加载因子(0.75)。

HashSet​(int initialCapacity)

构造一个新的空集; 支持HashMap实例具有指定的初始容量和默认加载因子(0.75)。

HashSet​(int initialCapacity, float loadFactor)

构造一个新的空集; 支持HashMap实例具有指定的初始容量和指定的加载因子。

HashSet​(Collection<? extends E> c)

构造一个包含指定集合中元素的新集合。

 常用方法API

变量和类型方法描述
booleanadd​(E e)

如果指定的元素尚不存在,则将其添加到此集合中。

voidclear()

从该集中删除所有元素。

Objectclone()

返回此 HashSet实例的浅表副本:未克隆元素本身。

booleancontains​(Object o)

如果此set包含指定的元素,则返回 true 。

booleanisEmpty()

如果此集合不包含任何元素,则返回 true 。

Iterator<E>iterator()

返回此set中元素的迭代器。

booleanremove​(Object o)

如果存在,则从该集合中移除指定的元素。

intsize()

返回此集合中的元素数(基数)。

Spliterator<E>spliterator()

在此集合中的元素上创建late-binding失败快速 Spliterator 。

  详细介绍地址:HashSet

TreeSet的使用

TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口。
TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。
TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
TreeSet 实现了Cloneable接口,意味着它能被克隆。
TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。
TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。
另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。 

构造方法

构造器 描述 TreeSet()

构造一个新的空树集,根据其元素的自然顺序进行排序。

TreeSet​(Collection<? extends E> c)

构造一个新的树集,其中包含指定集合中的元素,并根据其元素的 自然顺序进行排序 。

TreeSet​(Comparator<? super E> comparator)

构造一个新的空树集,根据指定的比较器进行排序。

TreeSet​(SortedSet<E> s)

构造一个包含相同元素并使用与指定有序集相同排序的新树集。

常用方法API

变量和类型方法描述
booleanadd​(E e)

如果指定的元素尚不存在,则将其添加到此集合中。

booleanaddAll​(Collection<? extends E> c)

将指定集合中的所有元素添加到此集合中。

Eceiling​(E e)

返回此set中大于或等于给定元素的 null元素,如果没有这样的元素,则 null 。

voidclear()

从该集中删除所有元素。

Objectclone()

返回此 TreeSet实例的浅表副本。

booleancontains​(Object o)

如果此set包含指定的元素,则返回 true 。

Iterator<E>descendingIterator()

以降序返回此集合中元素的迭代器。

NavigableSet<E>descendingSet()

返回此set中包含的元素的逆序视图。

Efirst()

返回此集合中当前的第一个(最低)元素。

Efloor​(E e)

返回此set中小于或等于给定元素的最大元素,如果没有这样的元素,则 null 。

SortedSet<E>headSet​(E toElement)

返回此set的部分视图,其元素严格小于 toElement 。

NavigableSet<E>headSet​(E toElement, boolean inclusive)

返回此set的部分视图,其元素小于(或等于,如果 inclusive为true) toElement 。

Ehigher​(E e)

返回此集合中的最小元素严格大于给定元素,如果没有这样的元素,则 null 。

booleanisEmpty()

如果此集合不包含任何元素,则返回 true 。

Iterator<E>iterator()

以升序返回此集合中元素的迭代器。

Elast()

返回此集合中当前的最后一个(最高)元素。

Elower​(E e)

返回此集合中的最大元素严格小于给定元素,如果没有这样的元素,则 null 。

EpollFirst()

检索并删除第一个(最低)元素,如果此组为空,则返回 null 。

EpollLast()

检索并删除最后一个(最高)元素,如果此集合为空,则返回 null 。

booleanremove​(Object o)

如果存在,则从该集合中移除指定的元素。

intsize()

返回此集合中的元素数(基数)。

Spliterator<E>spliterator()

在此集合中的元素上创建late-binding故障快速 Spliterator 。

NavigableSet<E>subSet​(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)

返回此set的部分视图,其元素范围为 fromElement到 toElement 。

SortedSet<E>subSet​(E fromElement, E toElement)

返回此set的部分视图,其元素范围从 fromElement (含)到 toElement (独占)。

SortedSet<E>tailSet​(E fromElement)

返回此set的部分视图,其元素大于或等于 fromElement 。

NavigableSet<E>tailSet​(E fromElement, boolean inclusive)

返回此set的部分视图,其元素大于(或等于,如果 inclusive为true) fromElement 。

 详细介绍地址:TreeSet

HashMap的使用

基于哈希表的Map接口的实现。 此实现提供了所有可选的映射操作,并允许null值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的并且允许空值。)

该实现为基本操作( getput )提供了恒定时间性能,假设散列函数在桶之间正确地分散元素。 对集合视图的迭代需要与HashMap实例的“容量”(桶数)加上其大小(键值映射的数量)成比例的时间。 因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子太低)非常重要。

HashMap的实例有两个影响其性能的参数: 初始容量负载因子 。 容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 加载因子是在自动增加容量之前允许哈希表获取的完整程度的度量。 当哈希表中的条目数超过加载因子和当前容量的乘积时,哈希表将被重新哈希(即,重建内部数据结构),以便哈希表具有大约两倍的桶数。

作为一般规则,默认加载因子(.75)在时间和空间成本之间提供了良好的折衷。 较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括getput )。 在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以便最小化重新散列操作的数量。 如果初始容量大于最大条目数除以加载因子,则不会发生重新加载操作。

如果要将多个映射存储在HashMap实例中,则使用足够大的容量创建映射将允许映射更有效地存储,而不是根据需要执行自动重新散列来扩展表。 请注意,使用具有相同hashCode()许多键是减慢任何哈希表性能的可靠方法。 为了改善影响,当键为Comparable时 ,此类可以使用键之间的比较顺序来帮助打破关系。

请注意,此实现不同步。 如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。)这通常通过同步自然封装映射的某个对象来完成。 。 如果不存在此类对象,则应使用Collections.synchronizedMap方法“包装”地图。 这最好在创建时完成,以防止意外地不同步访问地图:

  Map m = Collections.synchronizedMap(new HashMap(...)); 

所有这个类的“集合视图方法”返回的迭代器都是快速失败的 :如果在创建迭代器之后的任何时候对映射进行结构修改,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器以尽力而为的方式抛出ConcurrentModificationException 。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。

此类是Java Collections Framework的成员。

构造方法

构造器 描述 HashMap()

使用默认初始容量(16)和默认加载因子(0.75)构造一个空 HashMap 。

HashMap​(int initialCapacity)

使用指定的初始容量和默认加载因子(0.75)构造一个空 HashMap 。

HashMap​(int initialCapacity, float loadFactor)

使用指定的初始容量和加载因子构造一个空 HashMap 。

HashMap​(Map<? extends K,​? extends V> m)

构造一个新的 HashMap ,其映射与指定的 Map相同。

 常用方法API

变量和类型方法描述
voidclear()

从此映射中删除所有映射。

Objectclone()

返回此 HashMap实例的浅表副本:未克隆键和值本身。

Vcompute​(K key, BiFunction<? super K,​? super V,​? extends V> remappingFunction)

尝试计算指定键及其当前映射值的映射(如果没有当前映射, null )。

VcomputeIfAbsent​(K key, Function<? super K,​? extends V> mappingFunction)

如果指定的键尚未与值关联(或映射到 null ),则尝试使用给定的映射函数计算其值并将其输入此映射,除非 null 。

VcomputeIfPresent​(K key, BiFunction<? super K,​? super V,​? extends V> remappingFunction)

如果指定键的值存在且为非null,则尝试在给定键及其当前映射值的情况下计算新映射。

booleancontainsKey​(Object key)

如果此映射包含指定键的映射,则返回 true 。

booleancontainsValue​(Object value)

如果此映射将一个或多个键映射到指定值,则返回 true 。

Set<Map.Entry<K,​V>>entrySet()

返回此映射中包含的映射的Set视图。

Vget​(Object key)

返回指定键映射到的值,如果此映射不包含键的映射,则返回 null 。

booleanisEmpty()

如果此映射不包含键 - 值映射,则返回 true 。

Set<K>keySet()

返回此映射中包含的键的Set视图。

Vmerge​(K key, V value, BiFunction<? super V,​? super V,​? extends V> remappingFunction)

如果指定的键尚未与值关联或与null关联,则将其与给定的非空值关联。

Vput​(K key, V value)

将指定的值与此映射中的指定键相关联。

voidputAll​(Map<? extends K,​? extends V> m)

将指定映射中的所有映射复制到此映射。

Vremove​(Object key)

从此映射中删除指定键的映射(如果存在)。

intsize()

返回此映射中键 - 值映射的数量。

Collection<V>values()

返回此映射中包含的值的Collection视图。

详细介绍地址:HashMap

TreeMap的使用

基于红黑树的NavigableMap实现。 该地图是根据排序natural ordering其密钥,或通过Comparator在地图创建时提供,这取决于所使用的构造方法。

此实现提供了保证的log(n)时间成本containsKey , get , putremove操作。 算法是对Cormen,Leiserson和Rivest的算法导论中的算法的改编。

请注意,如果此有序映射要正确实现Map接口,则树映射维护的排序(如任何已排序的映射,以及是否提供显式比较器)必须equals一致 。 (有关与equals一致的精确定义,请参阅ComparableComparator )这是因为Map接口是根据equals操作定义的,但是有序映射使用其compareTo (或compare )方法执行所有键比较,因此从排序映射的角度来看,通过此方法被视为相等的键是相等的。 即使排序与equals不一致,也可以很好地定义有序映射的行为。 它只是没有遵守Map接口的一般合同。

请注意,此实现不同步。 如果多个线程同时访问映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅仅更改与现有键关联的值不是结构修改。)这通常通过在自然封装映射的某个对象上进行同步来实现。 如果不存在此类对象,则应使用Collections.synchronizedSortedMap方法“包装”地图。 这最好在创建时完成,以防止意外地不同步访问地图:

  SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...)); 

由此类的所有“集合视图方法”返回的集合的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时候对映射进行结构修改,除非通过迭代器自己的remove方法,迭代器将抛出一个ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器会尽最大努力抛出ConcurrentModificationException 。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。

Map.Entry的方法返回的所有Map.Entry对及其视图表示生成时映射的快照。 他们支持Entry.setValue方法。 (但请注意,可以使用put更改关联映射中的映射。)

此类是Java Collections Framework的成员。

构造方法

构造器 描述 TreeMap()

使用其键的自然顺序构造一个新的空树图。

TreeMap​(Comparator<? super K> comparator)

构造一个新的空树图,根据给定的比较器排序。

TreeMap​(Map<? extends K,​? extends V> m)

构造一个新的树映射,其中包含与给定映射相同的映射,根据其键的 自然顺序排序 。

TreeMap​(SortedMap<K,​? extends V> m)

构造一个包含相同映射的新树映射,并使用与指定有序映射相同的顺序。

  常用方法API

变量和类型方法描述
Map.Entry<K,​V>ceilingEntry​(K key)

返回与大于或等于给定键的最小键关联的键 - 值映射,如果没有此键,则 null 。

KceilingKey​(K key)

返回大于或等于给定键的 null键,如果没有这样的键,则 null 。

voidclear()

从此映射中删除所有映射。

Objectclone()

返回此 TreeMap实例的浅表副本。

booleancontainsKey​(Object key)

如果此映射包含指定键的映射,则返回 true 。

booleancontainsValue​(Object value)

如果此映射将一个或多个键映射到指定值,则返回 true 。

NavigableSet<K>descendingKeySet()

返回此映射中包含的键的反向顺序NavigableSet视图。

NavigableMap<K,​V>descendingMap()

返回此映射中包含的映射的逆序视图。

Set<Map.Entry<K,​V>>entrySet()

返回此映射中包含的映射的Set视图。

Map.Entry<K,​V>firstEntry()

返回与此映射中的最小键关联的键 - 值映射,如果映射为空,则 null 。

KfirstKey()

返回此映射中当前的第一个(最低)键。

Map.Entry<K,​V>floorEntry​(K key)

返回与小于或等于给定键的最大键关联的键 - 值映射,如果没有此键,则 null 。

KfloorKey​(K key)

返回小于或等于给定键的最大键,如果没有这样的键,则 null 。

Vget​(Object key)

返回指定键映射到的值,如果此映射不包含键的映射,则返回 null 。

SortedMap<K,​V>headMap​(K toKey)

返回此映射的部分视图,其键严格小于 toKey 。

NavigableMap<K,​V>headMap​(K toKey, boolean inclusive)

返回此映射的部分视图,其键小于(或等于,如果 inclusive为真) toKey 。

Map.Entry<K,​V>higherEntry​(K key)

返回与严格大于给定键的最小键关联的键 - 值映射,如果没有此键,则 null 。

KhigherKey​(K key)

返回严格大于给定键的最小键,如果没有这样的键,则返回 null 。

Set<K>keySet()

返回此映射中包含的键的Set视图。

Map.Entry<K,​V>lastEntry()

返回与此映射中的最大键关联的键 - 值映射,如果映射为空,则 null 。

KlastKey()

返回此映射中当前的最后一个(最高)键。

Map.Entry<K,​V>lowerEntry​(K key)

返回与严格小于给定键的最大键相关联的键 - 值映射,如果没有这样的键,则 null 。

KlowerKey​(K key)

返回严格小于给定键的最大键,如果没有这样键,则返回 null 。

NavigableSet<K>navigableKeySet()

返回此映射中包含的键的NavigableSet视图。

Map.Entry<K,​V>pollFirstEntry()

删除并返回与此映射中的最小键关联的键 - 值映射,如果映射为空,则 null 。

Map.Entry<K,​V>pollLastEntry()

删除并返回与此映射中的最大键关联的键 - 值映射,如果映射为空,则 null 。

Vput​(K key, V value)

将指定的值与此映射中的指定键相关联。

voidputAll​(Map<? extends K,​? extends V> map)

将指定映射中的所有映射复制到此映射。

Vremove​(Object key)

如果存在,则从此TreeMap中删除此键的映射。

intsize()

返回此映射中键 - 值映射的数量。

NavigableMap<K,​V>subMap​(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)

返回此映射部分的视图,其键范围为 fromKey至 toKey 。

SortedMap<K,​V>subMap​(K fromKey, K toKey)

返回此映射部分的视图,其键的范围从 fromKey (包括 toKey )到 toKey (独占)。

SortedMap<K,​V>tailMap​(K fromKey)

返回此映射的部分视图,其键大于或等于 fromKey 。

NavigableMap<K,​V>tailMap​(K fromKey, boolean inclusive)

返回此映射的部分视图,其键大于(或等于,如果 inclusive为真) fromKey 。

Collection<V>values()

返回此映射中包含的值的Collection视图。

详细介绍地址:TreeMap

自动拆箱和自动装箱

(1)自动装箱

Java自动将基本数据类型转换为包装类型,也就是int→Integer,实际上是调用了方法Integer.valueOf(int)。

(2)自动拆箱

Java自动将包装类型转换为基本数据类型,也就是Integer→int,实际上是调用了方法Integer.intValue()。

基本数据类型对应包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

什么是类型擦除?

Java在编译后的(.class)文件中是不包含泛型中的类型信息的,使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉,这个过程就称为类型擦除。

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();        
System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为 List<String>和 List<Integer>在 jvm 中的 Class 都是 List.class。泛型信息被擦除了。泛型类被类型擦除后,相应的类型就被替换成 Object 类型
在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

public class Erasure <T extends String>{
//  public class Erasure <T>{T object;public Erasure(T object) {this.object = object;}}public static void main(String[] args) {Erasure<String> str = new Erasure<String>();System.out.println(l1.getClass() == l2.getClass());     }
  • 此时打印结果为Erasure.class
  • 在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

注解

注解的定义和应用场景

注解的定义

注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。

应用场景

应用场景一:自定义注解+拦截器 实现登录校验

使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。

首先定义一个LoginRequired注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {}

然后写两个简单的接口,访问sourceA,sourceB资源

@RestController
public class IndexController {@GetMapping("/sourceA")public String sourceA(){return "你正在访问sourceA资源";}@GetMapping("/sourceB")public String sourceB(){return "你正在访问sourceB资源";}
}

 没添加拦截器之前成功访问

 实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

public class SourceAccessInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("进入拦截器了");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");}
}

 拦截成功如下:

在sourceB方法上添加我们的登录注解@LoginRequired:

@RestController
public class IndexController {@GetMapping("/sourceA")public String sourceA(){return "你正在访问sourceA资源";}@LoginRequired@GetMapping("/sourceB")public String sourceB(){return "你正在访问sourceB资源";}
}

 简单实现登录拦截逻辑

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("进入拦截器了");// 反射获取方法上的LoginRequred注解HandlerMethod handlerMethod = (HandlerMethod)handler;LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);if(loginRequired == null){return true;}// 有LoginRequired注解说明需要登录,提示用户登录response.setContentType("application/json; charset=utf-8");response.getWriter().print("你访问的资源需要登录");return false;
}

运行成功,访问sourceB时需要登录了,访问sourceA则不用登录。 

应用场景二:自定义注解+AOP 实现日志打印 

先导入切面需要的依赖包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义一个注解@MyLog

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {}

 定义一个切面类,见如下代码注释理解:

@Aspect // 1.表明这是一个切面类
@Component
public class MyLogAspect {// 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名// 切面最主要的就是切点,所有的故事都围绕切点发生// logPointCut()代表切点名称@Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")public void logPointCut(){};// 3. 环绕通知@Around("logPointCut()")public void logAround(ProceedingJoinPoint joinPoint){// 获取方法名称String methodName = joinPoint.getSignature().getName();// 获取入参Object[] param = joinPoint.getArgs();StringBuilder sb = new StringBuilder();for(Object o : param){sb.append(o + "; ");}System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());// 继续执行方法try {joinPoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println(methodName + "方法执行结束");}
}

在步骤二中的IndexController写一个sourceC进行测试,加上我们的自定义注解:

@MyLog
@GetMapping("/sourceC/{source_name}")
public String sourceC(@PathVariable("source_name") String sourceName){return "你正在访问sourceC资源";
}

 启动springboot web项目,输入访问地址

注解的本质和原理

注解本质是一个继承了Annotation接口的特殊接口,当你用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,如果允许的话就会将注解信息写入元素的属性表。当通过反射获取注解时,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到memberValues这个map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类对象,并初始化好处理器。通过代理对象调用注解的方法时,最终调用AnnotationInvocationHandler 的invoke方法。该方法会从memberValues 这个Map 中索引出对应的值并设置进使用这个注解的元素中去(memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值,memberValues 的来源是Java 常量池),一句话概括就是,通过方法名返回注解属性值。

扩展

AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler, 这个类的设计也非常有意思。

这里有一个 memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。

而这个 invoke 方法就很有意思了,大家注意看,我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来。

var2 指向被调用的方法实例,而这里首先用变量 var4 获取该方法的简明名称,接着 switch 结构判断当前的调用方法是谁,如果是 Annotation 中的四大方法,将 var7 赋上特定的值。如果当前调用的方法是 toString,equals,hashCode,annotationType 的话,AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。

那么假如 var7 没有匹配上这四种方法,说明当前的方法调用的是自定义注解字节声明的方法,这种情况下,将从我们的注解 map 中获取这个注解属性对应的值。一句话概括就是,通过方法名返回注解属性值。

好文参考:注解机制及其原理什么是注解注解的使用注解的原理 - 腾讯云开发者社区-腾讯云

元注解和预置注解

元注解

『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

这是我们 @Override 注解的定义,你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息。

JAVA 中有以下几个『元注解』:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解

其中,@Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。

@Target 的定义如下:

我们可以通过以下的方式来为这个 value 传值:

@Target(value = {ElementType.FIELD})

被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

@Retention 用于指明当前注解的生命周期,它的基本定义如下:

同样的,它也有一个 value 属性:

@Retention(value = RetentionPolicy.RUNTIME

这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:

  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。

剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

 预置注解

除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@Override 注解想必是大家很熟悉的了,它的定义如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。

所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。

@Deprecated 的基本定义如下:

依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。

当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。

@SuppressWarnings 主要用来压制 java 的警告,它的基本定义如下:

它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:

public static void main(String[] args) {Date date = new Date(2018, 7, 11);
}

这么一段代码,程序启动时编译器会报一个警告。

Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时

而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {Date date = new Date(2018, 7, 11);
}

这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。

当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。

自定义注解的相关内容就不再赘述了,比较简单,通过类似以下的语法即可自定义一个注解。

public @interface InnotationName{}

当然,自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。

 好文参考:JAVA 注解的基本原理 - Single_Yam - 博客园

IO流

File类的基本操作

  • File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  • File类声明在java.io包下
  • File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
  • 后续 File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

File类的构造方法

  •     File(String filePath)
  •     File(String parentPath,String childPath)
  •     File(File parentFile,String childPath)


常用方法API

方法名介绍
public String getAbsolutePath()
获取绝对路径
public String getPath()
获取路径
public String getName()
获取名称
public String getParent()
获取上层文件目录路径。若无,返回null
public long length()
获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified()
获取最后一次的修改时间,毫秒值
public String[] list()
获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles()
获取指定目录下的所有文件或者文件目录的File数组
public boolean renameTo(File dest)
把文件重命名为指定的文件路径
public boolean isDirectory()
判断是否是文件目录
public boolean isFile()
判断是否是文件
public boolean exists()
判断是否存在
public boolean canRead()
判断是否可读
public boolean canWrite()
判断是否可写
public boolean isHidden()
判断是否隐藏
public boolean createNewFile()
创建文件。若文件存在,则不创建,返回false
public boolean mkdir()
创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs()
创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建、
public boolean delete()
删除文件或者文件夹

好文参考: File类操作_巛小文子......的博客-CSDN博客_file 相关操作

Reader字符读取

java 中的 IO 输入流不是只有 InputStream 还有按字符输入的 Reader。

 和 InputStream 一样,Reader 也是所有字符输入流的超类。主要的方法是:public int read() throws IOException,read() 读取字符流中的下一个字符,返回 0-65535 的 int 类型数值, 返回 -1 表示已经读取结束。

FileReader

FileReader 打开一个文件并获取到文件的字符流。FileReader 用于读取文件中的内容。

private void fileReaderDemo() throws Exception {Reader reader = new FileReader("D:\\readerDemo.txt");int n;while ((n = reader.read()) != -1) {System.out.print((char)n);}reader.close();
}

Reader 实现了 Closeable 接口,可以用 try(Reader reader = new FileReader("D:\\readerDemo.txt")) {} 的方式关闭掉资源。

InputStreamReader

InputStreamReader 就是将 InputStream 读取的字节流装换为 Reader 的字符流。可以把任意的 InputStream 转换为 Reader,FileReader 就继承自 InputStreamReader。在创建 InputStreamReader 实例对象的时候可以指定字符集,以防止乱码。

private void inputStreamReaderDemo() throws Exception {InputStream inputStream = new FileInputStream("D:\\readerDemo.txt");try(Reader reader = new InputStreamReader(inputStream, "utf-8")) {int n;while ((n = reader.read()) != -1) {System.out.print((char)n);}}
}

StringReader 和 CharArrayReader

FileReader 是将文件作为一个读取源,StringReader 将 string 字符串作为一个读取源。

private void stringReaderDemo() throws Exception {try(Reader reader =  new StringReader("这是测试代码")) {char[] buffer = new char[1024];while ((reader.read(buffer)) != -1) {System.out.print(buffer);}}
}

reader.read(char[] buffer) 是 reader 读取字符流的重载方法,将内容不在是一个 char 一个 char 的输出,而是将内容读取到缓冲区 buffer 后一次性输出。

CharArrayReader 和 StringReader 几乎一样,调用方法变成了 try(Reader reader = new CharArrayReader("这是测试代码".toCharArray()))

BufferedReader

提供通用的缓冲方式读取文本并且提供了 readLine() 读取了一个文本行。从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。

private void bufferedReaderDemo() throws Exception {try(BufferedReader reader =  new BufferedReader(new FileReader("D:\\readerDemo.txt"))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}}
}

总结

介绍了几种常用 Reader 输入流的使用方式。FileReader 用于文件读取,BufferedReader 自带缓冲区读取效率高,StringReader 和 CharArrayReader 可以读取字符串源,InputStreamReader 将 InputStream 转为 Reader。

Writer字符输出

Writer是输出字符流的⽗父类,它是⼀一个抽象类

public void write(int c) throws IOException

讲解:直接将int型数据作为参数的话,是不不会写⼊入数字的,⽽而是现将数字按照ascll码表转换为相应的字符,然后写⼊入

public void write(String str) throws IOException
讲解:要想实现数字和中⽂文的写⼊入,必须要⽤用String为参数的Write

public abstract void write(char cbuf[], int off, int len) throws IOException;
讲解:将cbuf字符数组的⼀一部分写⼊入到流中,但能不能写len个字符取决于cbuf中是否有那么多
void flush() throws IOException
讲解:write是写到缓冲区中,可以认为是内存中,当缓冲区满时系统会⾃自动将缓冲区的内容写⼊入⽂文件,但是⼀一般还有⼀一部分有可能会留留在内存这个缓冲区中, 所以需要调⽤用flush空缓冲区数据。
void close() throws IOException
讲解:关闭输入流并释放与该流关联的系统资源

FileWriter用来写出字符文件的实现类 

 public FileWriter(String fileName) throws IOException
讲解:如果⽂文件不不存在,这会⾃自动创建。如果⽂文件存在,则会覆盖
public FileWriter(File file) throws IOException
讲解:如果⽂文件不不存在,这会⾃自动创建。如果⽂文件存在,则会覆盖
public FileWriter(String fileName, boolean append) throws
IOException
讲解:加⼊入true参数,会实现对⽂文件的续写,使⽤用false则会实现对⽂文件的覆盖
public FileWriter(File file, boolean append) throws IOException
讲解:加⼊入true参数,会实现对⽂文件的续写,使⽤用false则会实现对⽂文件的覆盖

 实战

package chapter12;import javax.imageio.IIOException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class WriterTest {public static void main(String [] args)throws Exception {test2();}public static void test2()throws Exception {String dir="C:\\Users\\联想\\Desktop\\test\\2.txt";Writer writer = new FileWriter(dir,true);writer.write(23567);writer.write(28404);writer.write(35838);writer.write(22530);writer.write("23567");writer.flush();writer.close();}public static void test1()throws Exception {String dir="C:\\Users\\联想\\Desktop\\test\\2.txt";Writer writer = new FileWriter(dir);writer.write(23567);writer.write(28404);writer.write(35838);writer.write(22530);writer.write("23567");writer.flush();writer.close();}
}

 好文参考:Java进阶核⼼之Reader、Writer字符流_小白~一看就懂的博客-CSDN博客_java输入输出之writer类之字符数据输出

发布评论

评论列表 (0)

  1. 暂无评论