「JavaSE」-常用类

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

常用类

Object类

Object是所有类的父类,任何类都默认继承Object。理论上Object类是所有类的父类,即直接或间接的继承java.lang.Object类。由于所有的类都继承在Object类,因此省略了extends Object关键字。

该类中主要有以下方法:

  • toString()
  • getClass()
  • equals()
  • clone()
  • finalize()

其中 toString() , getClass() , equals() 是其中最重要的方法。

【演示:查看Object类源码】

注意: Object类中的getClass(),notify(),notifyAll(),wait()等方法被定义为final类型,因此不能重写。

\

1、clone()方法

详解文章:

详解Java中的clone方法 -- 原型模式

protected native Object clone() throws CloneNotSupportedException;
复制代码

clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。

创建对象的方式:

  • 使用new操作符创建一个对象
  • 使用clone方法复制一个对象

相同点和区别:

  • new操作符的本意是分配内存。
    • 程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。
    • 分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
  • 而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

clone与copy的区别

假设现在有一个Employee对象,Employee tobby =new Employee(“CMTobby”,5000)

通常我们会有这样的赋值Employee cindyelf=tobby,这个时候只是简单了copy了一下reference,cindyelf和tobby都指向内存中同一个object,这样cindyelf或者tobby的一个操作都可能影响到对方。打个比方,如果我们通过cindyelf.raiseSalary()方法改变了salary域的值,那么tobby通过getSalary()方法得到的就是修改之后的salary域的值,显然这不是我们愿意看到的。我们希望得到tobby的一个精确拷贝,同时两者互不影响,这时候, 我们就可以使用Clone来满足我们的需求。Employee cindy=tobby.clone(),这时会生成一个新的Employee对象,并且和tobby具有相同的属性值和方法。

Shallow Clone与Deep Clone

主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用,我们有时候不希望在方法里将参数改变,这是就需要在类中复写clone方法(实现深复制)。

Clone是如何完成的呢?

Object在对某个对象实施Clone时对其是一无所知的,它仅仅是简单地执行域对域的copy,这就是Shallow Clone。

以Employee为例,它里面有一个域hireDay不是基本数据类型的变量,而是一个reference变量,经过Clone之后就会产生一个新的Date型的reference,它和原始对象中对应的域指向同一个Date对象,这样克隆类就和原始类共享了一部分信息,而这样显然是不利的,过程下图所示:

这个时候我们就需要进行deep Clone了,对那些非基本类型的域进行特殊的处理,例如本例中的hireDay。我们可以重新定义Clone方法,对hireDay做特殊处理,如下代码所示:

class Employee implements Cloneable {
  public Object clone() throws CloneNotSupportedException {
    Employee cloned = (Employee) super.clone();
    cloned.hireDay = (Date) hireDay.clone()
      return cloned;
  }
}
复制代码

clone方法的保护机制

在Object中Clone()是被声明为protected的,这样做是有一定的道理的,以Employee类为例,通过声明为protected,就可以保证只有Employee类里面才能“克隆”Employee对象.

clone方法的使用

什么时候使用shallow Clone,什么时候使用deep Clone,这个主要看具体对象的域是什么性质的,基本型别还是reference variable

调用Clone()方法的对象所属的类(Class)必须implements Clonable接口,否则在调用Clone方法的时候会抛出CloneNotSupportedException

2、toString()方法

public String toString() {
  return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
复制代码

Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。

该方法用得比较多,一般子类都有覆盖

我们推荐在学习阶段所有有属性的类都加上toString() 方法!

public static void main(String[] args){
  Object o1 = new Object();
  System.out.println(o1.toString());
}
复制代码

3、getClass()方法

public final native Class<?> getClass();
复制代码

返回此Object的运行时类类型。

不可重写,要调用的话,一般和getName()联合使用,如getClass().getName();

public static void main(String[] args) {
  Object o = new Object();
  System.out.println(o.getClass());
  //class java.lang.Object
}
复制代码

4、finalize()方法

protected void finalize() throws Throwable { }
复制代码

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

Java允许在类中定义一个名为finalize()的方法。它的工作原理是:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法。并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

关于垃圾回收,有三点需要记住:

  • 对象可能不被垃圾回收。只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。
  • 垃圾回收并不等于“析构”。

【科普:析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。】

  • 垃圾回收只与内存有关。使用垃圾回收的唯一原因是为了回收程序不再使用的内存。

finalize()的用途:

无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。

这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。不过这种情况一般发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。

5、equals()方法

public boolean equals(Object obj) {
  return (this == obj);
}
复制代码

Object中的equals方法是直接判断this和obj本身的值是否相等,即用来判断调用equals的对象和形参obj所引用的对象是否是同一对象,所谓同一对象就是指内存中同一块存储单元,如果this和obj指向的hi同一块内存对象,则返回true,如果this和obj指向的不是同一块内存,则返回false。

注意:即便是内容完全相等的两块不同的内存对象,也返回false。

如果是同一块内存,则object中的equals方法返回true,如果是不同的内存,则返回false

如果希望不同内存但相同内容的两个对象equals时返回true,则我们需要重写父类的equal方法

String类已经重写了object中的equals方法(这样就是比较内容是否相等了)

【演示:查看String类源码equals方法】

public boolean equals(Object anObject) {
  if (this == anObject) {
    return true;
  }
  if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = value.length;
    if (n == anotherString.value.length) {
      char v1[] = value;
      char v2[] = anotherString.value;
      int i = 0;
      while (n-- != 0) {
        if (v1[i] != v2[i])
          return false;
        i++;
      }
      return true;
    }
  }
  return false;
}
复制代码

6、hashCode()方法

public native int hashCode();
复制代码

返回该对象的哈希码值。

该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash Code() == obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

7、wait()方法

public final void wait() throws InterruptedException {
  wait(0);
}
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException
{
  if (timeout < 0) {
    throw new IllegalArgumentException("timeout value is negative");
  }
  if (nanos < 0 || nanos > 999999) {
    throw new IllegalArgumentException(
      "nanosecond timeout value out of range");
  }
  if (nanos > 0) {
    timeout++;
  }
  wait(timeout);
}
复制代码

可以看到有三种重载,wait什么意思呢?

方法中的异常:

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。

wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生:

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

8、notify()方法

public final native void notify();
复制代码

该方法唤醒在该对象上等待的某个线程。

public final native void notifyAll();
复制代码

该方法唤醒在该对象上等待的所有线程。

包装类

1、包装类介绍

虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。

这种借助于非面向对象技术的做法有时也会带来不便,比如引用类型数据均继承了 Object 类的特性,要转换为 String 类型(经常有这种需要)时只要简单调用 Object 类中定义的toString()即可,而基本数据类型转换为 String 类型则要麻烦得多。

为解决此类问题 ,Java为每种基本数据类型分别设计了对应的类,称之为包装类(Wrapper Classes),也有教材称为外覆类或数据类型类。

基本数据类型 对应的包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean

每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的方法。包装类对象一经创建,其内容(所封装的基本类型数据值)不可改变。

基本类型和对应的包装类可以相互装换:

  • 由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象;
  • 包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象重新简化为 int。

2、包装类的应用

1)实现 int 和 Integer 的相互转换

可以通过 Integer 类的构造方法将 int 装箱,通过 Integer 类的 intValue 方法将 Integer 拆箱。

public static void main(String[] args) {
  int m = 500;
  Integer obj = new Integer(m); // 手动装箱
  int n = obj.intValue(); // 手动拆箱
  System.out.println("n = " + n);
  Integer obj1 = new Integer(500);
  System.out.println("obj 等价于 obj1?" + obj.equals(obj1));
}
复制代码

2)将字符串转换为整数

Integer 类有一个静态的 paseInt() 方法,可以将字符串转换为整数,语法为:

parseInt(String s, int radix);
复制代码

s 为要转换的字符串,radix 为进制,可选,默认为十进制。

下面的代码将会告诉你什么样的字符串可以转换为整数:

public static void main(String[] args) {
  String[] str = {"123", "123abc", "abc123", "abcxyz"};
  for(String str1 : str){
    try{
      int m = Integer.parseInt(str1, 10);
      System.out.println(str1 + " 可以转换为整数 " + m);
    }catch(Exception e){
      System.out.println(str1 + " 无法转换为整数");
    }
  }
}

//结果
123 可以转换为整数 123
123abc 无法转换为整数
abc123 无法转换为整数
abcxyz 无法转换为整数
复制代码

3)将整数转换为字符串

Integer 类有一个静态的 toString() 方法,可以将整数转换为字符串,或者直接在整数后面加空字符串即可!

public static void main(String[] args) {
  int m = 500;
  String s = Integer.toString(m);
  String s2 = m+"";
  System.out.println("s = " + s);
}
复制代码

3、自动拆箱和装箱

上面的例子都需要手动实例化一个包装类,称为手动拆箱装箱。Java 1.5(5.0) 之前必须手动拆箱装箱。

Java 1.5 之后可以自动拆箱装箱,即在进行基本数据类型和对应的包装类转换时,系统将自动进行,这将大大方便程序员的代码书写。

public static void main(String[] args) {
  int m = 500;
  Integer obj = m; // 自动装箱
  int n = obj; // 自动拆箱
  System.out.println("n = " + n);
  Integer obj1 = 500;
  System.out.println("obj 等价于 obj1?" + obj.equals(obj1));
}

//结果:
// n = 500
// obj 等价于 obj1?true
复制代码

自动装箱与拆箱的功能事实上是编译器来帮您的忙,编译器在编译时期依您所编写的语法,决定是否进行装箱或拆箱动作。例如:

Integer i = 100;
相当于编译器自动为您作以下的语法编译:
Integer i = new Integer(100);
复制代码

所以自动装箱与拆箱的功能是所谓的“编译器蜜糖”(Compiler Sugar),虽然使用这个功能很方便,但在程序运行阶段您得了解Java的语义。例如下面的程序是可以通过编译的:

Integer i = null;
int j = i;
复制代码

这样的语法在编译时期是合法的,但是在运行时期会有错误,因为这种写法相当于:

Integer i = null;
int j = i.intValue();
复制代码

null表示i 没有参考至任何的对象实体,它可以合法地指定给对象参考名称。由于实际上i并没有参考至任何的对象,所以也就不可能操作intValue()方法,这样上面的写法在运行时会出现NullPointerException错误。

自动拆箱装箱是常用的一个功能,需要重点掌握。

一般地,当需要使用数字的时候,我们通常使用内置数据类型,如:byte、int、long、double 等。

然而,在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情形。

为了解决这个问题,Java 语言为每一个内置数据类型提供了对应的包装类。

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。

System类

  • System类代表系统,系统级的很多属性和方法都放置在该类的内部。该类位于java.lang包。
  • 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便地进行调用。
  • 成员变量
    • System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入)、标准输出流(显示器)和标准错误输出流(显示器)。
  • 成员方法
    • native long currentTimeMillis();
    该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1好0时0分0秒之间所差的毫秒数;
    复制代码
    • void exit(int status);
    该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。
    使用该方法可以在图形界面编程中实现程序的退出功能等。
    复制代码
    • void gc();
    该方法的作用是请求系统进行垃圾回收。
    至于系统是否立刻进行回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
    复制代码
    • String getProperty(String key);
    该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下:
    -   java.version     java运行时的环境版本
    -   java.home    java安装目录
    -   os.name      操作系统的名称
    -   os.version   操作系统的版本
    -   user.name    用户的账户名称
    -   user.home    用户的主目录
    -   user.dir     用户的当前工作目录
    复制代码

Math类

Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。

Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。

【演示:查看Math类的源码】

public final class Math{
//数学方法
}
复制代码

【常用值与函数】

  • Math.PI 记录的圆周率
  • Math.E 记录e的常量
  • Math中还有一些类似的常量,都是一些工程数学常用量。
  • Math.abs 求绝对值
  • Math.sin 正弦函数 Math.asin 反正弦函数
  • Math.cos 余弦函数 Math.acos 反余弦函数
  • Math.tan 正切函数 Math.atan 反正切函数 Math.atan2 商的反正切函数
  • Math.toDegrees 弧度转化为角度 Math.toRadians 角度转化为弧度
  • Math.ceil 得到不小于某数的最大整数
  • Math.floor 得到不大于某数的最大整数
  • Math.IEEEremainder 求余
  • Math.max 求两数中最大
  • Math.min 求两数中最小
  • Math.sqrt 求开方
  • Math.pow 求某数的任意次方, 抛出ArithmeticException处理溢出异常
  • Math.exp 求e的任意次方
  • Math.log10 以10为底的对数
  • Math.log 自然对数
  • Math.rint 求距离某数最近的整数(可能比某数大,也可能比它小)
  • Math.round 同上,返回int型或者long型(上一个函数返回double型)
  • Math.random 返回0,1之间的一个随机数
public static void main(String[] args) {
  /**
*Math.sqrt()//计算平方根
*Math.cbrt()//计算立方根
*Math.pow(a, b)//计算a的b次方
*Math.max( , );//计算最大值
*Math.min( , );//计算最小值
*/
  System.out.println(Math.sqrt(16)); //4.0
  System.out.println(Math.cbrt(8)); //2.0
  System.out.println(Math.pow(3,2)); //9.0
  System.out.println(Math.max(2.3,4.5));//4.5
  System.out.println(Math.min(2.3,4.5));//2.3
  /**
* abs求绝对值
*/
  System.out.println(Math.abs(-10.4)); //10.4
  System.out.println(Math.abs(10.1)); //10.1
  /**
* ceil天花板的意思,就是返回大的值
*/
  System.out.println(Math.ceil(-10.1)); //-10.0
  System.out.println(Math.ceil(10.7)); //11.0
  System.out.println(Math.ceil(-0.7)); //-0.0
  System.out.println(Math.ceil(0.0)); //0.0
  System.out.println(Math.ceil(-0.0)); //-0.0
  System.out.println(Math.ceil(-1.7)); //-1.0
  /**
* floor地板的意思,就是返回小的值
*/
  System.out.println(Math.floor(-10.1)); //-11.0
  System.out.println(Math.floor(10.7)); //10.0
  System.out.println(Math.floor(-0.7)); //-1.0
  System.out.println(Math.floor(0.0)); //0.0
  System.out.println(Math.floor(-0.0)); //-0.0
  /**
* random 取得一个大于或者等于0.0小于不等于1.0的随机数 [0,1)
*/
  System.out.println(Math.random()); //小于1大于0的double类型的数
  System.out.println(Math.random()+1);//大于1小于2的double类型的数
  /**
* rint 四舍五入,返回double值
* 注意.5的时候会取偶数 异常的尴尬=。=
*/
  System.out.println(Math.rint(10.1)); //10.0
  System.out.println(Math.rint(10.7)); //11.0
  System.out.println(Math.rint(11.5)); //12.0
  System.out.println(Math.rint(10.5)); //10.0
  System.out.println(Math.rint(10.51)); //11.0
  System.out.println(Math.rint(-10.5)); //-10.0
  System.out.println(Math.rint(-11.5)); //-12.0
  System.out.println(Math.rint(-10.51)); //-11.0
  System.out.println(Math.rint(-10.6)); //-11.0
  System.out.println(Math.rint(-10.2)); //-10.0
  /**
* round 四舍五入,float时返回int值,double时返回long值
*/
  System.out.println(Math.round(10.1)); //10
  System.out.println(Math.round(10.7)); //11
  System.out.println(Math.round(10.5)); //11
  System.out.println(Math.round(10.51)); //11
  System.out.println(Math.round(-10.5)); //-10
  System.out.println(Math.round(-10.51)); //-11
  System.out.println(Math.round(-10.6)); //-11
  System.out.println(Math.round(-10.2)); //-10
}
复制代码

Random类

Java中存在着两种Random函数:

1)java.lang.Math.Random;

调用这个Math.Random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是[0.0,1.0)的左闭右开区间,返回值是一个伪随机选择的数,在该范围内(近似)均匀分布。例子如下:

public static void main(String[] args) {
  // 结果是个double类型的值,区间为[0.0,1.0)
  System.out.println("Math.random()=" + Math.random());
  int num = (int) (Math.random() * 3);
  // 注意不要写成(int)Math.random()*3,这个结果为0或1,因为先执行了强制转换
  System.out.println("num=" + num);
}
//结果
//Math.random()=0.44938147153848396
//num=1
复制代码

2)java.util.Random

下面是Random()的两种构造方法:

  • Random():创建一个新的随机数生成器。
  • Random(long seed):使用单个 long 种子创建一个新的随机数生成器。

你在创建一个Random对象的时候可以给定任意一个合法的种子数,种子数只是随机算法的起源数字,和生成的随机数的区间没有任何关系。

如下面的Java代码:

【演示一】

在没带参数构造函数生成的Random对象的种子缺省是当前系统时间的毫秒数。

rand.nextInt(100)中的100是随机数的上限,产生的随机数为0-100的整数,不包括100。

public static void main(String[] args) {
  Random rand =new Random();
  int i=rand.nextInt(100);
  System.out.println(i);
}
复制代码

【演示二】

对于种子相同的Random对象,生成的随机数序列是一样的。

public static void main(String[] args) {
  Random ran1 = new Random(25);
  System.out.println("使用种子为25的Random对象生成[0,100)内随机整数序列: ");
  for (int i = 0; i < 10; i++) {
    System.out.print(ran1.nextInt(100) + " ");
  }
  System.out.println();
}
复制代码

【方法摘要】

  • protected int next(int bits):生成下一个伪随机数。
  • boolean nextBoolean():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的boolean值。
  • void nextBytes(byte[] bytes):生成随机字节并将其置于用户提供的 byte 数组中。
  • double nextDouble():返回下一个伪随机数,它是取自此随机数生成器序列的、在0.0和1.0之间均匀分布的 double值。
  • float nextFloat():返回下一个伪随机数,它是取自此随机数生成器序列的、在0.0和1.0之间均匀分布float值。
  • double nextGaussian():返回下一个伪随机数,它是取自此随机数生成器序列的、呈高斯(“正态”)分布的double值,其平均值是0.0标准差是1.0。
  • int nextInt():返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。
  • int nextInt(int n):返回一个伪随机数,它是取自此随机数生成器序列的、在(包括和指定值(不包括)之间均匀分布的int值。
  • long nextLong():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 long 值。
  • void setSeed(long seed):使用单个 long 种子设置此随机数生成器的种子。

【例子】

  1. 生成[0,1.0)区间的小数:double d1 = r.nextDouble();
  1. 生成[0,5.0)区间的小数:double d2 = r.nextDouble() * 5;
  1. 生成[1,2.5)区间的小数:double d3 = r.nextDouble() * 1.5 + 1;
  1. 生成[0,10)区间的整数:int n2 = r.nextInt(10);

BigInteger类与BigDecimal类

1、BigInteger类

  • Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的,最大为263-1。如果要表示再大的整数,不管是基本数据类型还是它们的包装类都无能为力了,更别说作运算了。
  • java.math包的BigInteger可以表示不可变的任何精度的整数。BigInteger提供所以Java的基本整数操作符的对应物,并提供java.lang.Math的所有相关方法。另外,BigInteger还提供一下运算:模算术、GCD计算、质数测试、素数生成、位操作以及一些其他操作。
  • 构造器
    • BigInteger(String val);    根据字符串构建BigInteger对象
  • 常用方法

    • public BigInteger abs();
      返回此BigInteger的绝对值的BigInteger。

    • BigInteger add(BigInteger val);
      返回其值为(this + val)的BigInteger。

    • BigInteger subtract(BigInteger val);
      返回其值为(this - val)的BigInteger。

    • BigInteger multiply(BigInteger val);
      返回其值为(this * val)的BigInteger。

    • BigInteger divide(BigInteger val);
      返回其值为(this / val)的BigInteger。整数相除只保留整数部分。

    • BigInteger remainder(BigInteger val);
      返回其值为(this % val)的BigInteger。

    • BigInteger[] divideAndRemainder(BigInteger val);
      返回包含(this / val)的后跟(this % val)的两个BigInteger的数组;

    • BigInteger pow(int exponent);
      返回其值为(thisexponent)的BigInteger。

2、BigDecimal类

  • 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到 java.math.BigDecimal 类
  • BigDecimal 类支持不可变的、任意精度的有符号十进制定点数。
  • 构造器

    • public BigDecimal(double val)

    • public BigDecimal(String val)

  • 常用方法

    • public BigDecimal add(BigDecimal augend)

    • public BigDecimal subtract(BigDecimal subtrahend)

    • public BigDecimal multiply(BigDecimal multiplicand)

    • public BigDecimal devide(BigDecimal divisor,int scale,int roundingMode)

日期时间类

JDK8之前的日期时间

  • java.lang.System

    • System类提供的 public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差,称为时间戳

    • 此方法适用于计算时间差

  • java.util.Date

    • 表示特定的瞬间,精确到毫秒

    • 构造器:

      • Date():使用无参构造器创建当前时间的Date对象

      • Date(long date):创建指定毫秒数的Date对象

    • 常用方法

      • getTime():返回自1970年1月1日00:00:00GMT之间的时间差(时间戳)

      • toString():显示当前的年、月、日、时、分、秒

  • java.sql.Date

    • 对应数据可中的日期类型的变量

    • 实例化 new java.sql.Date(long date);

    • 将java.util.Date对象转换成java.sql.Date对象

    • 情况一:Date date4 = new java.sql.Date(12345678L);
      java.sql.Date date5 = (java.sql.Date) date4;

    • 情况二:Date date6 = new Date();
      java.sql.Date date7 = new java.sql.Date(date6.getTime());

  • java.text.SimpleDateFormat

    • 是一个不与语言环境有关的方式来格式化和解析日期的具体类

    • 格式化:日期 -->字符串   解析:字符串 --> 日期

    • 格式化

      • SimpleDateFormat() :默认的模式和语言环境创建对象

      • public SimpleDateFormat(String pattern) :该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:

        • public String format(Date date) 方法格式化时间对象date
    • 解析

      • public Date parse(String source) :从给定字符串的开始解析文本,以生成一个日期
  • java.util.Calendar()

    • Calendar是一个抽象基类,主要用于完成日期字段之间相互操作的功能。

    • 获取Calendar实例的方法

      • 使用Calendar.getInstance()静态方法

      • 调用它的子类GregorianCalendar的构造器

    • 一个Calendar的实例是系统时间的抽象表示,通过:(常用方法)

      • get(int field)  :获取想要的时间信息。

      • set(int field,int value)

      • add(int field,int amount)

      • getTime() :日历类 --> Date

      • setTime(Date date)  :Date --> 日历类

    • 注意:

      • 获取月份时:一月是0,二月是1,以此类推,12月是11

      • 获取星期时:周日是1,周二是2,...,周六是7

1、Date类

java.util 包提供了 Date 类来封装当前的日期和时间。

Date 类提供两个构造函数来实例化 Date 对象。

第一个构造函数使用当前日期和时间来初始化对象。

Date()
复制代码

第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。

Date(long millisec)
复制代码

Date对象创建以后,可以调用下面的方法。

序号 方法和描述
1 boolean after(Date date) 若当调用此方法的Date对象在指定日期之后返回true,否则返回false。
2 boolean before(Date date) 若当调用此方法的Date对象在指定日期之前返回true,否则返回false。
3 Object clone( ) 返回此对象的副本。
4 int compareTo(Date date) 比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。
5 int compareTo(Object obj) 若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。
6 boolean equals(Object date) 当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。
7 long getTime( ) 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
8 int hashCode( ) 返回此对象的哈希码值。
9 void setTime(long time) 用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。
10 String toString( ) 把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。

【演示:获取当前日期时间】

Java中获取当前日期和时间很简单,使用 Date 对象的 toString() 方法来打印当前日期和时间

如下所示:

public static void main(String args[]) {
  // 初始化 Date 对象
  Date date = new Date();
  // 使用 toString() 函数显示日期时间
  System.out.println(date.toString());
  //Sat Apr 27 15:09:43 CST 2019
}
复制代码

【演示:日期比较】

  • 使用 getTime() 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
public static void main(String[] args) {
  // 初始化 Date 对象
  Date date = new Date();
  long time = date.getTime();
  long time2 = date.getTime();
  System.out.println(time==time2);
}
复制代码
  • 使用方法 before(),after() 和 equals() 。例如,一个月的12号比18号早,则 new Date(99, 2,12).before(new Date (99, 2, 18)) 返回true。
public static void main(String[] args) {
  boolean before = new Date(97, 01, 05).before(new Date(99, 11, 16));
  System.out.println(before);
}
复制代码

2、SimpleDateFormat

【演示:使用 SimpleDateFormat 格式化日期】

SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。例如:

public static void main(String args[]) {
  Date dNow = new Date( );
  SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
  System.out.println("当前时间为: " + ft.format(dNow));
}
复制代码

其中 yyyy 是完整的公元年,MM 是月份,dd 是日期,HH:mm:ss 是时、分、秒。

注意:有的格式大写,有的格式小写,例如 MM 是月份,mm 是分;HH 是 24 小时制,而 hh 是 12 小时制。时间模式字符串用来指定时间格式。在此模式中,所有的 ASCII 字母被保留为模式字母,定义如下:

字母 描述 示例
G 纪元标记 AD
y 四位年份 2001
M 月份 July or 07
d 一个月的日期 10
h A.M./P.M. (1~12)格式小时 12
H 一天中的小时 (0~23) 22
m 分钟数 30
s 秒数 55
S 毫秒数 234
E 星期几 Tuesday
D 一年中的日子 360
F 一个月中第几周的周几 2 (second Wed. in July)
w 一年中第几周 40
W 一个月中第几周 1
a A.M./P.M. 标记 PM
k 一天中的小时(1~24) 24
K A.M./P.M. (0~11)格式小时 10
z 时区 Eastern Standard Time
' 文字定界符 Delimiter
" 单引号 `

【演示:使用printf格式化日期】

Java 格式化输出 printf 例子

printf 方法可以很轻松地格式化时间和日期。使用两个字母格式,它以 %t 开头并且以下面表格中的一个字母结尾。

public static void main(String args[]) {
  // 初始化 Date 对象
  Date date = new Date();
  //c的使用
  System.out.printf("全部日期和时间信息:%tc%n",date);
  //f的使用
  System.out.printf("年-月-日格式:%tF%n",date);
  //d的使用
  System.out.printf("月/日/年格式:%tD%n",date);
  //r的使用
  System.out.printf("HH:MM:SS PM格式(12时制):%tr%n",date);
  //t的使用
  System.out.printf("HH:MM:SS格式(24时制):%tT%n",date);
  //R的使用
  System.out.printf("HH:MM格式(24时制):%tR",date);
}

//结果:
全部日期和时间信息:星期六 四月 27 15:23:45 CST 2019
年-月-日格式:2019-04-27
月/日/年格式:04/27/19
HH:MM:SS PM格式(12时制):03:23:45 下午
HH:MM:SS格式(24时制):15:23:45
HH:MM格式(24时制):15:23
复制代码

【时间休眠:休眠(sleep)】

sleep()使当前线程进入停滞状态(阻塞当前线程) ,让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会。

你可以让程序休眠一毫秒的时间或者到您的计算机的寿命长的任意段时间。例如,下面的程序会休眠3秒:

public static void main(String args[]) {
  try {
    System.out.println(new Date( ) + "\n");
    Thread.sleep(1000*3); // 休眠3秒
    System.out.println(new Date( ) + "\n");
  } catch (Exception e) {
    System.out.println("Got an exception!");
  }
}
复制代码

3、Calendar类

Calendar类的功能要比Date类强大很多,而且在实现方式上也比Date类要复杂一些。

Calendar类是一个抽象类,在实际使用时实现特定 的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。

创建一个代表系统当前日期的Calendar对象

public static void main(String args[]) {
  Calendar c = Calendar.getInstance();//默认是当前日期
  System.out.println(c);
}
//输出
java.util.GregorianCalendar[time=1556350818634,areFieldsSet=true,areAllFields Set=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offs
et=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null],firs
tDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=3,WEEK_OF_YEAR=17
,WEEK_OF_MONTH=4,DAY_OF_MONTH=27,DAY_OF_YEAR=117,DAY_OF_WEEK=7,DAY_OF_WEEK_IN
_MONTH=4,AM_PM=1,HOUR=3,HOUR_OF_DAY=15,MINUTE=40,SECOND=18,MILLISECOND=634,ZO
NE_OFFSET=28800000,DST_OFFSET=0]
复制代码

创建一个指定日期的Calendar对象

使用Calendar类代表特定的时间,需要首先创建一个Calendar的对象,然后再设定该对象中的年月日参数来完成。

//创建一个代表2019年4月27日的Calendar对象
Calendar c1 = Calendar.getInstance();
c1.set(2019, 4 - 1, 27);
复制代码

Calendar类对象字段类型

Calendar类中用以下这些常量表示不同的意义,jdk内的很多类其实都是采用的这种思想

常量 描述
Calendar.YEAR 年份
Calendar.MONTH 月份
Calendar.DATE 日期
Calendar.DAY_OF_MONTH 日期,和上面的字段意义完全相同
Calendar.HOUR 12小时制的小时
Calendar.HOUR_OF_DAY 24小时制的小时
Calendar.MINUTE 分钟
Calendar.SECOND
Calendar.DAY_OF_WEEK 星期几
// 获得年份
int year = c1.get(Calendar.YEAR);
// 获得月份
int month = c1.get(Calendar.MONTH) + 1;
// 获得日期
int date = c1.get(Calendar.DATE);
// 获得小时
int hour = c1.get(Calendar.HOUR_OF_DAY);
// 获得分钟
int minute = c1.get(Calendar.MINUTE);
// 获得秒
int second = c1.get(Calendar.SECOND);
// 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)
  int day = c1.get(Calendar.DAY_OF_WEEK);
复制代码

【演示:设置完整日期】

c1.set(2009, 6 - 1, 12);//把Calendar对象c1的年月日分别设这为:2009、6、12
复制代码

【演示:设置某个字段】

c1.set(Calendar.DATE,10);
c1.set(Calendar.YEAR,2008);
//其他字段属性set的意义以此类推
复制代码

【add设置】

//把c1对象的日期加上10,也就是c1也就表示为10天后的日期,其它所有的数值会被重新计算
c1.add(Calendar.DATE, 10);
//把c1对象的日期减去10,也就是c1也就表示为10天前的日期,其它所有的数值会被重新计算
c1.add(Calendar.DATE, -10);
复制代码

【演示:GregorianCalendar】

public static void main(String args[]) {
  String months[] = {
    "Jan", "Feb", "Mar", "Apr",
    "May", "Jun", "Jul", "Aug",
    "Sep", "Oct", "Nov", "Dec"};
  int year;
  // 初始化 Gregorian 日历
  // 使用当前时间和日期
  // 默认为本地时间和时区
  GregorianCalendar gcalendar = new GregorianCalendar();
  // 显示当前时间和日期的信息
  System.out.print("Date: ");
  System.out.print(months[gcalendar.get(Calendar.MONTH)]);
  System.out.print(" " + gcalendar.get(Calendar.DATE) + " ");
  System.out.println(year = gcalendar.get(Calendar.YEAR));
  System.out.print("Time: ");
  System.out.print(gcalendar.get(Calendar.HOUR) + ":");
  System.out.print(gcalendar.get(Calendar.MINUTE) + ":");
  System.out.println(gcalendar.get(Calendar.SECOND));
  // 测试当前年份是否为闰年
  if(gcalendar.isLeapYear(year)) {
    System.out.println("当前年份是闰年");
  }
  else {
    System.out.println("当前年份不是闰年");
  }
}
//输出:
Date: Apr 27 2019
Time: 3:56:20
当前年份不是闰年
复制代码

注意:Calender的月份是从0开始的,但日期和年份是从1开始的

【演示】

public static void main(String[] args) {
  Calendar c1 = Calendar.getInstance();
  c1.set(2017, 1, 1);
  System.out.println(c1.get(Calendar.YEAR)
                     +"-"+c1.get(Calendar.MONTH)
                     +"-"+c1.get(Calendar.DATE));
  c1.set(2017, 1, 0);
  System.out.println(c1.get(Calendar.YEAR)
                     +"-"+c1.get(Calendar.MONTH)
                     +"-"+c1.get(Calendar.DATE));
}

//输出
2017-1-1
2017-0-31
复制代码

可见,将日期设为0以后,月份变成了上个月,但月份可以为0,把月份改为2试试:

public static void main(String[] args) {
  Calendar c1 = Calendar.getInstance();
  c1.set(2017, 2, 1);
  System.out.println(c1.get(Calendar.YEAR)
                     +"-"+c1.get(Calendar.MONTH)
                     +"-"+c1.get(Calendar.DATE));
  c1.set(2017, 2, 0);
  System.out.println(c1.get(Calendar.YEAR)
                     +"-"+c1.get(Calendar.MONTH)
                     +"-"+c1.get(Calendar.DATE));
}
//输出
2017-2-1
2017-1-28
复制代码

可以看到上个月的最后一天是28号,所以Calendar.MONTH为1的时候是2月 。

Java8中新的日期时间API

  • java.time — 包含值对象的基础包
  • java.time.chrono — 提供对不同的日历系统的访问
  • java.time.format — 格式化和解析时间和日期
  • java.time.temporal — 包括底层框架和拓展特性
  • java.time.zone — 包含时区支持的类

说明:大多数开发者只会用到基础包和format包,也可能用到temporal包。

LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime类是其中重要的几个类,它们的实例对象是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与失去相关的信息。

ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

  • LocalDate代表ISO格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
  • LocalTime表示一个时间,而不是日期。
  • LocalDateTime是用来表示日期和时间的,这是一个最常用的类。
/**
now(); 获取当前的日期、时间、日期和时间;
of();  获取指定的年、月、日、时、分、秒(没有偏移量);

getXxx(); 获取相关的属性;
getDayOfMonth();/getDayOfYear();  获取月份天数(1-31)/获取年份天数(1-366);
getDayOfWeek();  获取星期几,返回一个DayOfWeek枚举值;
getMonth();  获得月份,返回一个Month枚举值;
getMonthValue();/getYear();  获得月份(1-12)/获得年份
getHour();/getMinute();/getSecond();  获取当前对象对应的小时、分钟、秒

withXxx();  设置相关的属性; 
withDayOfMonth();/withDayOfYear();/withMonth();/withYear();  设置月份天数、年份天数、月份、年份;
plusDays();/plusWeeks();/plusMonths();/plusYears();/plusHours();  添加几天、几周、几个月、几年、几小时;
minusDays();/minusWeeks();/minusMonths();/minusYears();/minusHours();  减去几天、几周、几月、几年、几小时;  

*/
复制代码

Instant瞬时

  • Instant:时间线上的一个瞬时点。这可能被用来记录应用程序中事件的时间戳。
  • java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上一个点,而不需要任何上下文信息,例如:时区。
  • 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。
  • (1ns = 10-9 s) 1秒=1000毫秒=106微妙=109纳秒
/**
now();  静态方法,返回默认UTC时区的Instant类的对象;获取对应的格林威治时间;
ofEpochMilli(long epochMilli);  静态方法,返回1979-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象;
atOffset(ZoneOffset offset);  结合即时的偏移来创建一个OffsetDateTime;
toEpochMilli();  返回1979-01-01 00:00:00到当前时间的毫秒数,即为时间戳;
*/
复制代码

时间戳是指格林威治时间1979-01-01 00:00:00(北京时间1979-01-01 08:00:00)起到现在的总秒数。

格式化与解析日期或时间

java.time.format.DateTimeFormat类:该类提供了三种格式化方法:

  • 预定义的标准格式;如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME;
  • 本地化相关的格式;如:ofLocalizedDateTime(FormatStyle.LONG);ofLocalizedDateTime(FormatStyle.MEDIUM);ofLocalizedDateTime(FormatStyle.SHORT);
  • 自定义的格式;如:ofPattern("yyyy-MM-dd hh:mm:ss E")
/**
ofPattern(String pattern);  静态方法,返回一个指定字符串格式的DateTimeFormatter;
format(TemporalAccessor t);  格式化一个日期、时间,返回字符串;
parse(CharSequence text);  将指定格式的字符序列解析为一个日期、时间;
*/
复制代码

String类

1、String概述

在API中是这样描述:

String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。

String是一个final类,代表不可变的字符序列

字符串是常量,它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。

String对象的字符内容是存储在一个字符数组value[]中的。

【演示:查看String源码】

public final class String
  implements java.io.Serializable, Comparable<String>, CharSequence{
  /** The value is used for character storage. */
  private final char value[];
  
  /** Cache the hash code for the string.*/
  private int hash;//Default to 0
}
复制代码
String:字符串,使用一堆""双引号引起来表示
1.String 声明为final,不可被继承
2.String 实现了 Serializable接口:表示字符串是支持序列化的
         实现了Comparable接口:表示字符串可以比较大小
3.String 内部定义了final char[] value用于存储字符串数据
4.String:代表不可变的字符序列。简称:不可变性
复制代码

【理解String的不可变性】

package com.wang.classtest.string;

import org.junit.Test;

/**
 * @Author wangjin
 * @Date 2022/03/27 10:46
 * @Description String的使用
 */
public class stringTest {
    /**
     * String:字符串,使用一堆""双引号引起来表示
     * 1.String 声明为final,不可被继承
     * 2.String 实现了 Serializable接口:表示字符串是支持序列化的
     * 实现了Comparable接口:表示字符串可以比较大小
     * 3.String 内部定义了final char[] value用于存储字符串数据
     * 4.String:代表不可变的字符序列。简称:不可变性
     *      体现:1)当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
     *           2)当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
     *           3)当调用String的replace()方法修改指定的字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
     * 5.通过字面量的方式(区别于new方式)给一个字符串赋值,此时字符串声明在字符串常量池中
     * 6.字符串常量常量池中不会
     */

    @Test
    public void test1() {
        String s1 = "abc";//字面量的定义方式
        String s2 = "abc";
        s1 = "hello";

        System.out.println(s1 == s2);//比较的是s1和s2的地址值

        System.out.println(s1);//hello
        System.out.println(s2);//abc

        String s3="abc";
        s3 += "def";
        System.out.println(s3);//abcdef

        String s4="abc";
        String s5 = s4.replace('a', 'm');
        System.out.println(s4);//abc
        System.out.println(s5);//mbc

    }
}
复制代码

【String的成员变量】

//String的属性值
private final char value[];

//数组被使用的开始位置
private final int offset;

//String中元素的个数
private final int count;

//String类型的hash值
private int hash; // Default to 0

private static final long serialVersionUID = -6849794470754667710L;

private static final ObjectStreamField[] serialPersistentFields =
  new ObjectStreamField[0];
复制代码

从源码看出String底层使用一个字符数组来维护的。

成员变量可以知道String类的值是final类型的,不能被改变的,所以只要一个值改变就会生成一个新的String类型对象,存储String数据也不一定从数组的第0个元素开始的,而是从offset所指的元素开始。

【String的构造方法】

String()
		//初始化一个新创建的 String 对象,使其表示一个空字符序列。
String(byte[] bytes)
		//通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
String(byte[] bytes, Charset charset)
		//通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。
String(byte[] bytes, int offset, int length)
		//通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。
String(byte[] bytes, int offset, int length, Charset charset)
		//通过使用指定的 charset 解码指定的 byte 子数组,构造一个新的 String。
String(byte[] bytes, int offset, int length, String charsetName)
		//通过使用指定的字符集解码指定的 byte 子数组,构造一个新的 String。
String(byte[] bytes, String charsetName)
		//通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。
String(char[] value)
		//分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
String(char[] value, int offset, int count)
		//分配一个新的 String,它包含取自字符数组参数一个子数组的字符。
String(int[] codePoints, int offset, int count)
		//分配一个新的 String,它包含 Unicode 代码点数组参数一个子数组的字符。
String(String original)
		//初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
String(StringBuffer buffer)
		//分配一个新的字符串,它包含字符串缓冲区参数中当前包含的字符序列。
String(StringBuilder builder)
		//分配一个新的字符串,它包含字符串生成器参数中当前包含的字符序列。
复制代码

2、创建字符串对象方式

  • 字面量的方式:直接赋值方式创建对象是在方法区的常量池
String str="hello";//直接赋值的方式
复制代码
  • new方法:通过构造方法创建字符串对象是在堆内存
String str=new String("hello");//实例化的方式
复制代码
//本质上 this.value = new char[0];
String s1 = new String();

//this.value = original.value;
String s2 = new String(String original1);

//this.value = Arrays.copyOf(value,value.length);
String s3 = new String(char[] a);

String s4 = new String(char[] a,int startIndex,int count);
复制代码

package com.wang.classtest.string;

import org.junit.Test;

/**
 * @Author wangjin
 * @Date 2022/03/27 11:56
 * @Description String的实例化方式
 */
public class stringDemo02 {
    /**
     * String的实例化方式
     * 方式一:通过字面量定义的方式
     * 方式二:通过 new + 构造器 的方式
     */

    @Test
    public void test() {
        //通过字面量定义的方式:此时s1和s2的数据javaEE声明在方法区中的字符串常量池中。
        String s1 = "javaEE";
        String s2 = "javaEE";

        //通过 new + 构造器 的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
        String s3 = new String("javaEE");
        String s4 = new String("javaEE");

        System.out.println(s1 == s2);//true
        System.out.println(s1 == s3);//false
        System.out.println(s1 == s4);//false
        System.out.println(s3 == s4);//false

    }
}
复制代码

【字符串对象是如何存储的?】

面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?

两个:一个是堆空间中new结构,另一个是char[]数组对应的常量池中的数据“abc”.

public class stringDemo02 {

    /**
     * 结论:
     * 1.常量与常量的拼接结果在常量池中,并且常量池中不会存在相同内容的常量
     * 2.只要其中有一个是变量,结果就在堆中
     * 3.如果拼接的结果调用intern()方法,返回值就在常量池中
     */

    @Test
    public void test01() {
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
        System.out.println(s6 == s7);//false

    }
复制代码

结论:

  • 常量与常量的拼接结果在常量池中,并且常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中
  • 如果拼接的结果调用intern()方法,返回值就在常量池中

【一道面试题】

public class StringTest(){
  String str = new String(original:"good");
  char[] ch = {'t','e','s','t'};
  
  public void change(String str,char ch[]){
    str = "test ok";
    ch[0] = 'b';
  }
  
  public static void main(String[] args){
    StringTest ex = new StringTest();
    ex.change(ex.str,ex.ch);
    System.out.println(ex.str);//good
    System.out.println(ex.ch);//best
  }
}
复制代码

【两种实例化方式的比较】

  • 编写代码比较
public static void main(String[] args) {
  String str1 = "Lance";
  String str2 = new String("Lance");
  String str3 = str2; //引用传递,str3直接指向st2的堆内存地址
  String str4 = "Lance";
  /**
* ==:
* 基本数据类型:比较的是基本数据类型的值是否相同
* 引用数据类型:比较的是引用数据类型的地址值是否相同
* 所以在这里的话:String类对象==比较,比较的是地址,而不是内容
*/
  System.out.println(str1==str2);//false
  System.out.println(str1==str3);//false
  System.out.println(str3==str2);//true
  System.out.println(str1==str4);//true
}
复制代码
  • 内存图分析

可能这里还是不够明显,构造方法实例化方式的内存图:String str = new String("Hello");

首先:

当我们再一次的new一个String对象时:

【字符串常量池】

在字符串中,如果采用直接赋值的方式(String str="Lance")进行对象的实例化,则会将匿名对象“Lance”放入对象池,每当下一次对不同的对象进行直接赋值的时候会直接利用池中原有的匿名对象,我们可以用对象手工入池;

public static void main(String args[]){
  String str =new String("Lance").intern();//对匿名对象"hello"进行手工入池操作
  String str1="Lance";
  System.out.println(str==str1);//true
}
复制代码

【两种实例化方式的区别】

  • 直接赋值(String str = "hello"):只开辟一块堆内存空间,并且会自动入池,不会产生垃圾。
  • 构造方法(String str= new String("hello");):会开辟两块堆内存空间,其中一块堆内存会变成垃圾被系统回收,而且不能够自动入池,需要通过public String intern();方法进行手工入池。
  • 在开发的过程中不会采用构造方法进行字符串的实例化。

【避免空指向】

首先了解: == 和public boolean equals()比较字符串的区别

==在对字符串比较的时候,对比的是内存地址,而equals比较的是字符串内容,在开发的过程中,equals()通过接受参数,可以避免空指向。

String str = null;
if(str.equals("hello")){//此时会出现空指向异常
  ...
}
if("hello".equals(str)){//此时equals会处理null值,可以避免空指向异常
  ...
}
复制代码

【String类对象一旦声明则不可以改变;而改变的只是地址,原来的字符串还是存在的,并且产生垃圾】

3、String常用的方法

//1.基本操作
int length():返回字符串的长度,return value.length
char charAt(int index):返回某索引处的字符,return value[index]
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引,从前往后找
int indexOf(String str,int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引,从后往前找
int lastindexOf(String str,int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
//注:indexOf() 和 lastIndexOf()方法如果未找到都是返回-1
  
//2.转换操作
char[] toCharArray():将此字符串转换为一个字符数组  char[] arr = str.toCharArray(); --str是字符串
String valueOf(int i):将int型数转换为字符串
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写  

//3.替换与去除
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的
String replace(CharSequence target,CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
String replaceAll(String regex, String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串
String replaceFirst(String regex, String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串  
String trim():返回字符串的副本,忽略前导空白和尾部空白
  
//4.连接、截取与分割
String concat(String str):将指定字符串连接到此字符串的尾部,等价于用 "+"
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串
String substring(int beginIndex, int endIndex):返回一个新的字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个字符串  

//5.判断操作
int compareTo(String antherString):比较两个字符串的大小
boolean isEmpty():判断是否是空字符,return value.length == 0
  
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String antherString):与equals方法类似,忽略大小写
  
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
  
//6.匹配
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
复制代码

1)String的判断

【常用方法】

  • boolean equals(Object obj):比较字符串的内容是否相同
  • boolean equalsIgnoreCase(String str): 比较字符串的内容是否相同,忽略大小写
  • boolean startsWith(String str): 判断字符串对象是否以指定的str开头
  • boolean endsWith(String str): 判断字符串对象是否以指定的str结尾

【演示】

public static void main(String[] args) {
  // 创建字符串对象
  String s1 = "hello";
  String s2 = "hello";
  String s3 = "Hello";
  
  // boolean equals(Object obj):比较字符串的内容是否相同
  System.out.println(s1.equals(s2)); //true
  System.out.println(s1.equals(s3)); //false
  System.out.println("-----------");
  
  // boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写
  System.out.println(s1.equalsIgnoreCase(s2)); //true
  System.out.println(s1.equalsIgnoreCase(s3)); //true
  System.out.println("-----------");
  
  // boolean startsWith(String str):判断字符串对象是否以指定的str开头
  System.out.println(s1.startsWith("he")); //true
  System.out.println(s1.startsWith("ll")); //false
}
复制代码

2)String的截取

【常用方法】

  • int length():获取字符串的长度,其实也就是字符个数
  • char charAt(int index):获取指定索引处的字符
  • int indexOf(String str):获取str在字符串对象中第一次出现的索引
  • String substring(int start):从start开始截取字符串
  • String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end

【演示】

public static void main(String args[]) {
  // 创建字符串对象
  String s = "helloworld";
  
  // int length():获取字符串的长度,其实也就是字符个数
  System.out.println(s.length()); //10
  System.out.println("--------");
  
  // char charAt(int index):获取指定索引处的字符
  System.out.println(s.charAt(0)); //h
  System.out.println(s.charAt(1)); //e
  System.out.println("--------");
  
  // int indexOf(String str):获取str在字符串对象中第一次出现的索引
  System.out.println(s.indexOf("l")); //2
  System.out.println(s.indexOf("owo")); //4
  System.out.println(s.indexOf("ak")); //-1
  System.out.println("--------");
  
  // String substring(int start):从start开始截取字符串
  System.out.println(s.substring(0)); //helloworld
  System.out.println(s.substring(5)); //world
  System.out.println("--------");
  
  // String substring(int start,int end):从start开始,到end结束截取字符串
  System.out.println(s.substring(0, s.length())); //helloworld
  System.out.println(s.substring(3, 8)); //lowor
}
复制代码

3)String的转换

【常用方法】

  • char[] toCharArray():把字符串转换为字符数组
  • String toLowerCase():把字符串转换为小写字符串
  • String toUpperCase():把字符串转换为大写字符串

【演示】

public static void main(String args[]) {
  // 创建字符串对象
  String s = "abcde";
  
  // char[] toCharArray():把字符串转换为字符数组
  char[] chs = s.toCharArray();
  for (int x = 0; x < chs.length; x++) {
    System.out.println(chs[x]);
  }
  System.out.println("-----------");
  
  // String toLowerCase():把字符串转换为小写字符串
  System.out.println("HelloWorld".toLowerCase());
  // String toUpperCase():把字符串转换为大写字符串
  System.out.println("HelloWorld".toUpperCase());
}
复制代码

4)其他方法

【常用方法】

  • 去除字符串两端空格:String trim()
  • 按照指定符号分割字符串:String[] split(String str)

【演示】

public static void main(String args[]) {
  // 创建字符串对象
  String s1 = "helloworld";
  String s2 = " helloworld ";
  String s3 = " hello world ";
  System.out.println("---" + s1 + "---");
  System.out.println("---" + s1.trim() + "---");
  System.out.println("---" + s2 + "---");
  System.out.println("---" + s2.trim() + "---");
  System.out.println("---" + s3 + "---");
  System.out.println("---" + s3.trim() + "---");
  System.out.println("-------------------");
  
  // String[] split(String str)
  // 创建字符串对象
  String s4 = "aa,bb,cc";
  String[] strArray = s4.split(",");
  for (int x = 0; x < strArray.length; x++) {
    System.out.println(strArray[x]);
  }
}
复制代码

4、String的不可变性

当我们去阅读源代码的时候,会发现有这样的一句话:

Strings are constant; their values cannot be changed after they are created.
复制代码

意思就是说:String是个常量,从一出生就注定不可变。String类被final修饰,官方注释说明创建后不能被改变。

【了解一个经典的面试题】

public static void main(String[] args) {
  String a = "abc";
  String b = "abc";
  String c = new String("abc");
  System.out.println(a==b); //true
  System.out.println(a.equals(b)); //true
  System.out.println(a==c); //false
  System.out.println(a.equals(c)); //true
}
复制代码

内存图分析:

【分析】

因为String太过常用,JAVA类库的设计者在实现时做了个小小的变化,即采用了享元模式,每当生成一个新内容的字符串时,他们都被添加到一个共享池中,当第二次再次生成同样内容的字符串实例时,就共享此对象,而不是创建一个新对象,但是这样的做法仅仅适合于通过=符号进行的初始化。

需要说明一点的是,在object中,equals()是用来比较内存地址的,但是String重写了equals()方法,用来比较内容的,即使是不同地址,只要内容一致,也会返回true,这也就是为什么a.equals(c)返回true的原因了。

【String不可变的好处】

  • 可以实现多个变量引用堆内存中的同一个字符串实例,避免创建的开销。
  • 我们的程序中大量使用了String字符串,有可能是出于安全性考虑。
  • 大家都知道HashMap中key为String类型,如果可变将变的多么可怕。
  • 当我们在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。

5、字符串常量池

【字符串常量池概述】

  1. 常量池表(Constant_Pool table)

Class文件中存储所有常量(包括字符串)的table。这是Class文件中的内容,还不是运行时的内容,不要理解它是个池子,其实就是Class文件中的字节码指令。

  • 运行时常量池(Runtime Constant Pool)

JVM内存中方法区的一部分,这是运行时的内容。这部分内容(绝大部分)是随着JVM运行时候,从常量池转化而来,每个Class对应一个运行时常量池。

  • 字符串常量池(String Pool)

这部分也在方法区中,但与Runtime Constant Pool不是一个概念,String Pool是JVM实例全局共享的,全局只有一个。JVM规范要求进入这里的String实例叫“被驻留的interned string”,各个JVM可以有不同的实现,HotSpot是设置了一个哈希表StringTable来引用堆中的字符串实例,被引用就是被驻留。

【亨元模式】

其实字符串常量池这个问题涉及到一个设计模式,叫“享元模式”,顾名思义 - - - > 共享元素模式也就是说:一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素

Java中String部分就是根据享元模式设计的,而那个存储元素的地方就叫做“字符串常量池 - String Pool”

【详细分析】

int x = 10;
String y = "hello";
复制代码
  1. 首先, 10 和"hello" 会在经过javac(或者其他编译器)编译过后变为Class文件中constant_pool table 的内容
  1. 当我们的程序运行时,也就是说JVM运行时,每个Class constant_pool table 中的内容会被加载到JVM内存中的方法区中各自Class的Runtime Constant Pool。
  1. 一个没有被String Pool包含的Runtime Constant Pool中的字符串(这里是"hello")会被加入到String Pool中(HosSpot使用hashtable引用方式),步骤如下:
    • 在Java Heap(堆)中根据"hello"字面量create一个字符串对象

    • 将字面量"hello"与字符串对象的引用在hashtable中关联起来,键 - 值形式是:"hello" = 对象的引用地址。

另外来说,当一个新的字符串出现在Runtime Constant Pool中时怎么判断需不需要在Java Heap中创建新对象呢?

策略是这样:会先去根据equals来比较Runtime Constant Pool中的这个字符串是否和String Pool中某一个是相等的(也就是找是否已经存在),如果有那么就不创建,直接使用其引用;反之,就如同上面的第三步。如此,就实现了享元模式,提高的内存利用效率。

举例:

使用String s = new String("hello");会创建几个对象
答:会创建2个对象
首先,出现了字面量"hello",那么去String Pool中查找是否有相同字符串存在,因为程序就这一行代码所以肯定没有,那么就在Java Heap中用字面量"hello"首先创建1个String对象。
接着,new String("hello"),关键字new又在Java Heap中创建了1个对象,然后调用接收String参数的构造器进行了初始化。最终s的引用是这个String对象.
复制代码

String 与基本数据类型、包装类之间的转换

  • String ---> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
  • 基本数据类型 ---> String:调用String重载的valueOf(xxx)方法
public void test(){
  String str1 = "123";
  int num = Integer.parseInt(str1);
  
  String str2 = String.valueOf(num);
  System.out.println(str2);//123
}
复制代码

String与字符数组转换

  • 字符数组 ---> 字符串

    • String类的构造器:String(char[])String(char[], int offset, int length)分别用字符数组中的全部字符和部分字符创建字符串对象。
  • 字符串 ---> 字符数组

    • public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法

    • public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

public void test(){
  String str1 = "abc123";
  
  //String --> char[]:调用String的toCharArray()方法
  //char[] --> String:调用String的构造器
  char[] charArray = str1.toCharArray();
  for(int i = 0;i < charArray.length;i++){
    System.out.println(charArray[i])
  }
  
  char[] arr = new char[]{'h','e','l','l','o'};
  String str2 = new String(arr);
  System.out.println(str2)
}
复制代码

String与字节数组转换

//String与byte[]之间的转换
//String --> byte[]:调用String的getBytes()方法
//byte[] --> String:调用String构造器

public void test(){
  String str1 = "abc123";
  byte[] bytes = str1.getBytes();//使用默认的字符集进行转换
  
  System.out.println(Arrays.toString(bytes));//[97,98,99,49,50,51]
  
  String str2 = new String(bytes);
  System.out.println(str2);
}
复制代码

StringBuilder和StringBuffer

1、概述

【演示:查看源码及API文档】

public final class StringBuilder
  extends AbstractStringBuilder
  implements java.io.Serializable, CharSequence{
}
复制代码

StringBuilder 是一个可变的字符序列。它继承于AbstractStringBuilder,实现了CharSequence接口。

StringBuffer 也是继承于AbstractStringBuilder的子类;但是,StringBuilder和StringBuffer不同,前者是非线程安全的,后者是线程安全的。

StringBuilder 和 CharSequence之间的关系图如下:

【源码概览】

package java.lang;

public final class StringBuilder
  extends AbstractStringBuilder
  implements java.io.Serializable, CharSequence {
  
  static final long serialVersionUID = 4383685877147921099L;
  
  // 构造函数。
  //默认的字符数组大小是16。
  public StringBuilder() {
    super(16);
  }
  
  // 构造函数。指定StringBuilder的字符数组大小是capacity。
  public StringBuilder(int capacity) {
    super(capacity);
  }
  
  // 构造函数。指定字符数组大小=str长度+15,且将str的值赋值到当前字符数组中。
  public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
  }
  
  // 构造函数。指定字符数组大小=seq长度+15,且将seq的值赋值到当前字符数组中。
  public StringBuilder(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
  }
  
  // 追加“对象obj对应的字符串”。String.valueOf(obj)实际上是调用obj.toString()
  public StringBuilder append(Object obj) {
    return append(String.valueOf(obj));
  }
  
  // 追加“str”。
  public StringBuilder append(String str) {
    super.append(str);
    return this;
  }
  
  // 追加“sb的内容”。
  private StringBuilder append(StringBuilder sb) {
    if (sb == null)
      return append("null");
    int len = sb.length();
    int newcount = count + len;
    if (newcount > value.length)
      expandCapacity(newcount);
    sb.getChars(0, len, value, count);
    count = newcount;
    return this;
  }
  
  // 追加“sb的内容”。
  public StringBuilder append(StringBuffer sb) {
    super.append(sb);
    return this;
  }
  
  // 追加“s的内容”。
  public StringBuilder append(CharSequence s) {
    if (s == null)
      s = "null";
    if (s instanceof String)
      return this.append((String)s);
    if (s instanceof StringBuffer)
      return this.append((StringBuffer)s);
    if (s instanceof StringBuilder)
      return this.append((StringBuilder)s);
    return this.append(s, 0, s.length());
  }
  
  // 追加“s从start(包括)到end(不包括)的内容”。
  public StringBuilder append(CharSequence s, int start, int end) {
    super.append(s, start, end);
    return this;
  }
  
  // 追加“str字符数组对应的字符串”
  public StringBuilder append(char[] str) {
    super.append(str);
    return this;
  }
  
  // 追加“str从offset开始的内容,内容长度是len”
  public StringBuilder append(char[] str, int offset, int len) {
    super.append(str, offset, len);
    return this;
  }
  // 追加“b对应的字符串”
  public StringBuilder append(boolean b) {
    super.append(b);
    return this;
  }
  
  // 追加“c”
  public StringBuilder append(char c) {
    super.append(c);
    return this;
  }
  
  // 追加“i”
  public StringBuilder append(int i) {
    super.append(i);
    return this;
  }
  
  // 追加“lng”
  public StringBuilder append(long lng) {
    super.append(lng);
    return this;
  }
  
  // 追加“f”
  public StringBuilder append(float f) {
    super.append(f);
    return this;
  }
  
  // 追加“d”
  public StringBuilder append(double d) {
    super.append(d);
    return this;
  }
  // 追加“codePoint”
  public StringBuilder appendCodePoint(int codePoint) {
    super.appendCodePoint(codePoint);
    return this;
  }
  
  // 删除“从start(包括)到end的内容”
  public StringBuilder delete(int start, int end) {
    super.delete(start, end);
    return this;
  }
  
  // 删除“位置index的内容”
  public StringBuilder deleteCharAt(int index) {
    super.deleteCharAt(index);
    return this;
  }
  
  // “用str替换StringBuilder中从start(包括)到end(不包括)的内容”
  public StringBuilder replace(int start, int end, String str) {
    super.replace(start, end, str);
    return this;
  }
  
  // “在StringBuilder的位置index处插入‘str中从offset开始的内容’,插入内容长度是len”
    public StringBuilder insert(int index, char[] str, int offset,
                                int len)
  {
    super.insert(index, str, offset, len);
    return this;
  }
  
  // “在StringBuilder的位置offset处插入obj对应的字符串”
  public StringBuilder insert(int offset, Object obj) {
    return insert(offset, String.valueOf(obj));
  }
  
  // “在StringBuilder的位置offset处插入str”
  public StringBuilder insert(int offset, String str) {
    super.insert(offset, str);
    return this;
  }
  
  // “在StringBuilder的位置offset处插入str”
  public StringBuilder insert(int offset, char[] str) {
    super.insert(offset, str);
    return this;
  }
  
  // “在StringBuilder的位置dstOffset处插入s”
  public StringBuilder insert(int dstOffset, CharSequence s) {
    if (s == null)
      s = "null";
    if (s instanceof String)
      return this.insert(dstOffset, (String)s);
    return this.insert(dstOffset, s, 0, s.length());
  }
  
  // “在StringBuilder的位置dstOffset处插入's中从start到end的内容'”
  public StringBuilder insert(int dstOffset, CharSequence s,
                              int start, int end)
  {
    super.insert(dstOffset, s, start, end);
    return this;
  }
  
  // “在StringBuilder的位置Offset处插入b”
  public StringBuilder insert(int offset, boolean b) {
    super.insert(offset, b);
    return this;
  }
  
  // “在StringBuilder的位置Offset处插入c”
  public StringBuilder insert(int offset, char c) {
    super.insert(offset, c);
    return this;
  }
  
  // “在StringBuilder的位置Offset处插入i”
  public StringBuilder insert(int offset, int i) {
    return insert(offset, String.valueOf(i));
  }
  
  // “在StringBuilder的位置Offset处插入l”
  public StringBuilder insert(int offset, long l) {
    return insert(offset, String.valueOf(l));
  }
  
  // “在StringBuilder的位置Offset处插入f”
  public StringBuilder insert(int offset, float f) {
    return insert(offset, String.valueOf(f));
  }
  
  // “在StringBuilder的位置Offset处插入d”
  public StringBuilder insert(int offset, double d) {
    return insert(offset, String.valueOf(d));
  }
  
  // 返回“str”在StringBuilder的位置
  public int indexOf(String str) {
    return indexOf(str, 0);
  }
  
  // 从fromIndex开始查找,返回“str”在StringBuilder的位置
  public int indexOf(String str, int fromIndex) {
    return String.indexOf(value, 0, count,
                          str.toCharArray(), 0, str.length(),
                          fromIndex);
  }
  
  // 从后向前查找,返回“str”在StringBuilder的位置
  public int lastIndexOf(String str) {
    return lastIndexOf(str, count);
  }
  
  // 从fromIndex开始,从后向前查找,返回“str”在StringBuilder的位置
  public int lastIndexOf(String str, int fromIndex) {
    return String.lastIndexOf(value, 0, count,
                              str.toCharArray(), 0, str.length(),
                              fromIndex);
  }
  
  // 反转StringBuilder
  public StringBuilder reverse() {
    super.reverse();
    return this;
  }
  
  public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
  }

  // 序列化对应的写入函数
  private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    s.defaultWriteObject();
    s.writeInt(count);
    s.writeObject(value);
  }
  
  // 序列化对应的读取函数
  private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    count = s.readInt();
    value = (char[]) s.readObject();
  }
}
复制代码
/**
String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列,底层使用char[]存储;
StringBuffer:可变的字符序列,线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列,jdk5.0新增的;线程不安全的,效率高;底层使用char[]存储
*/


//源码分析
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16] ;

/**
问题1:System.out.println(sb2.length()); //3
问题2:扩容问题-如果要添加的数据底层数组装不下了,那就需要扩容底层数组。
		默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
		
		指导意义:在开发中建议大家使用 StringBuffer(int capacity) 或者 StringBuilder(int capacity)
*/
复制代码

2、常用方法

StringBuffer的常用方法:
append(xxx):提供了很多的append()方法,用于进行字符串拼接
delete(int start,int end):删除指定位置的内容
replace(int start,int end,String str):把[start,end]位置替换为str
insert(int offset,xxx):在指定位置插入xxx
reverse():把当前的字符序列逆转
indexOf(String str):返回字符串str首次在StingBuffer中出现的位置
substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
length():返回StringBuffer字符串的长度
charAt(int n):返回指定位置的字符
setCharAt(int n,char ch):将指定位置的字符改成新的字符ch
 
/**
  总结:
  增:append(xxx);
  删:delete(int start,int end)
  改:setCharAt(int n,char ch)/replace(int start,int end,String str)
  查:charAt(int n)
  插:insert(int offset,xxx)
  长度:length()
  遍历:for()+charAt() / toString()
*/
复制代码

1)insert

private static void testInsertAPIs(){
  System.out.println("---------- testInsertAPIs -----------");
  
  StringBuilder sbuilder = new StringBuilder();
  
  // 在位置0处插入字符数组
  sbuilder.insert(0, new char[]{'a', 'b', 'c', 'd', 'e'});
  // 在位置0处插入字符数组。0表示字符数组起始位置,3表示长度
  sbuilder.insert(0, new char[]{'A', 'B', 'C', 'D', 'E'}, 0, 3);
  // 在位置0处插入float
  sbuilder.insert(0, 1.414f);
  // 在位置0处插入double
  sbuilder.insert(0, 3.14159d);
  // 在位置0处插入boolean
  sbuilder.insert(0, true);
  // 在位置0处插入char
  sbuilder.insert(0, '\n');
  // 在位置0处插入int
  sbuilder.insert(0, 100);
  // 在位置0处插入long
  sbuilder.insert(0, 12345L);
  // 在位置0处插入StringBuilder对象
  sbuilder.insert(0, new StringBuilder("StringBuilder"));
  // 在位置0处插入StringBuilder对象。6表示被在位置0处插入对象的起始位置(包括),13是结束位置(不包括)
    sbuilder.insert(0, new StringBuilder("STRINGBUILDER"), 6, 13);
  // 在位置0处插入StringBuffer对象。
  sbuilder.insert(0, new StringBuffer("StringBuffer"));
  // 在位置0处插入StringBuffer对象。6表示被在位置0处插入对象的起始位置(包括),12是结
  束位置(不包括)
    sbuilder.insert(0, new StringBuffer("STRINGBUFFER"), 6, 12);
  // 在位置0处插入String对象。
  sbuilder.insert(0, "String");
  // 在位置0处插入String对象。1表示被在位置0处插入对象的起始位置(包括),6是结束位置(不
  包括)
    sbuilder.insert(0, "0123456789", 1, 6);
  sbuilder.insert(0, '\n');
  
  // 在位置0处插入Object对象。此处以HashMap为例
  HashMap map = new HashMap();
  map.put("1", "one");
  map.put("2", "two");
  map.put("3", "three");
  
  sbuilder.insert(0, map);
  
  System.out.printf("%s\n\n", sbuilder);
}
复制代码

2)append

/**
* StringBuilder 的append()示例
*/
private static void testAppendAPIs() {
  
  System.out.println("------------------- testAppendAPIs -------------------");
  
  StringBuilder sbuilder = new StringBuilder();
  
  // 追加字符数组
  sbuilder.append(new char[]{'a','b','c','d','e'});
  // 追加字符数组。0表示字符数组起始位置,3表示长度
  sbuilder.append(new char[]{'A','B','C','D','E'}, 0, 3);
  // 追加float
  sbuilder.append(1.414f);
  // 追加double
  sbuilder.append(3.14159d);
  // 追加boolean
  sbuilder.append(true);
  // 追加char
  sbuilder.append('\n');
  // 追加int
  sbuilder.append(100);
  // 追加long
  sbuilder.append(12345L);
  // 追加StringBuilder对象
  sbuilder.append(new StringBuilder("StringBuilder"));
  // 追加StringBuilder对象。6表示被追加对象的起始位置(包括),13是结束位置(不包括)
  sbuilder.append(new StringBuilder("STRINGBUILDER"), 6, 13);
  // 追加StringBuffer对象。
  sbuilder.append(new StringBuffer("StringBuffer"));
  // 追加StringBuffer对象。6表示被追加对象的起始位置(包括),12是结束位置(不包括)
  sbuilder.append(new StringBuffer("STRINGBUFFER"), 6, 12);
  // 追加String对象。
  sbuilder.append("String");
  // 追加String对象。1表示被追加对象的起始位置(包括),6是结束位置(不包括)
  sbuilder.append("0123456789", 1, 6);
  sbuilder.append('\n');
  
  // 追加Object对象。此处以HashMap为例
  HashMap map = new HashMap();
  map.put("1", "one");
  map.put("2", "two");
  map.put("3", "three");
  sbuilder.append(map);
  sbuilder.append('\n');
  
  // 追加unicode编码
  sbuilder.appendCodePoint(0x5b57); // 0x5b57是“字”的unicode编码
  sbuilder.appendCodePoint(0x7b26); // 0x7b26是“符”的unicode编码
  sbuilder.appendCodePoint(0x7f16); // 0x7f16是“编”的unicode编码
  sbuilder.appendCodePoint(0x7801); // 0x7801是“码”的unicode编码
  
  System.out.printf("%s\n\n", sbuilder);
}
复制代码

3)replace

/**
* StringBuilder 的replace()示例
*/
private static void testReplaceAPIs() {
  
  System.out.println("------------------- testReplaceAPIs-------------------");
  
  StringBuilder sbuilder;
  
  sbuilder = new StringBuilder("0123456789");
  sbuilder.replace(0, 3, "ABCDE");
  System.out.printf("sbuilder=%s\n", sbuilder);
  
  sbuilder = new StringBuilder("0123456789");
  sbuilder.reverse();
  System.out.printf("sbuilder=%s\n", sbuilder);
  
  sbuilder = new StringBuilder("0123456789");
  sbuilder.setCharAt(0, 'M');
  System.out.printf("sbuilder=%s\n", sbuilder);
  
  System.out.println();
}
复制代码

4)delete

private static void testDeleteAPIs() {
  
  System.out.println("------------------- testDeleteAPIs -------------------");
  
  StringBuilder sbuilder = new StringBuilder("0123456789");
  
  // 删除位置0的字符,剩余字符是“123456789”。
  sbuilder.deleteCharAt(0);
  // 删除位置3(包括)到位置6(不包括)之间的字符,剩余字符是“123789”。
  sbuilder.delete(3,6);
  
  // 获取sb中从位置1开始的字符串
  String str1 = sbuilder.substring(1);
  // 获取sb中从位置3(包括)到位置5(不包括)之间的字符串
  String str2 = sbuilder.substring(3, 5);
  // 获取sb中从位置3(包括)到位置5(不包括)之间的字符串,获取的对象是CharSequence对象,此处转型为String
    String str3 = (String)sbuilder.subSequence(3, 5);
  
  System.out.printf("sbuilder=%s\nstr1=%s\nstr2=%s\nstr3=%s\n",
                    sbuilder, str1, str2, str3);
}
复制代码

5)index

/**
* StringBuilder 中index相关API演示
*/
private static void testIndexAPIs() {
  System.out.println("-------------------------------- testIndexAPIs --------------------------------");
  
  StringBuilder sbuilder = new StringBuilder("abcAbcABCabCaBcAbCaBCabc");
  System.out.printf("sbuilder=%s\n", sbuilder);
  
  // 1. 从前往后,找出"bc"第一次出现的位置
  System.out.printf("%-30s = %d\n", "sbuilder.indexOf("bc")", sbuilder.indexOf("bc"));
  
  // 2. 从位置5开始,从前往后,找出"bc"第一次出现的位置
  System.out.printf("%-30s = %d\n", "sbuilder.indexOf("bc", 5)", sbuilder.indexOf("bc", 5));
  
  // 3. 从后往前,找出"bc"第一次出现的位置
  System.out.printf("%-30s = %d\n", "sbuilder.lastIndexOf("bc")", sbuilder.lastIndexOf("bc"));
  
  // 4. 从位置4开始,从后往前,找出"bc"第一次出现的位置
  System.out.printf("%-30s = %d\n", "sbuilder.lastIndexOf("bc", 4)",sbuilder.lastIndexOf("bc", 4));
  System.out.println();
}
复制代码

6)其他API

/**
* StringBuilder 的其它API示例
*/
private static void testOtherAPIs() {
  System.out.println("----------- testOtherAPIs -----------");
  StringBuilder sbuilder = new StringBuilder("0123456789");
  
  int cap = sbuilder.capacity();
  System.out.printf("cap=%d\n", cap);
  
  /*
    capacity()返回的是字符串缓冲区的容量
    StringBuffer( ); 分配16个字符的缓冲区
    StringBuffer( int len ); 分配len个字符的缓冲区
    StringBuffer( String s ); 除了按照s的大小分配空间外,再分配16个字符的缓冲区
    你的StringBuffer是用字符构造的,"0123456789"的长度是10另外再分配16个字符,所以一共是26。
	*/
  
  char c = sbuilder.charAt(6);
  System.out.printf("c=%c\n", c);
  
  char[] carr = new char[4];
  sbuilder.getChars(3, 7, carr, 0);
  for (int i=0; i<carr.length; i++){
    System.out.printf("carr[%d]=%c ", i, carr[i]);
  }
  System.out.println();
}
复制代码

3、StringBuffer

和StringBulider用法差不多,不过多介绍,主要看一下三者的区别

4、小结

【String、StringBuffer、StringBuilder之间的区别】

首先需要说明的是:

  • String 字符串常量
  • StringBuffer 字符串变量(线程安全)
  • StringBuilder 字符串变量(非线程安全)

在大多数情况下三者在 执行速度方面的比较:StringBuilder > StringBuffer > String

解释:

String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

为什么是大多数情况呢?

在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中,String 效率是远要比 StringBuffer 快的:

String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“
simple”).append(“ test”);
复制代码

你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个String S1 = “This is only a” + “ simple” + “test”;其实就是:String S1 = “This is only a simple test”;所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:

String S2 = “This is only a”;

String S3 = “ simple”;

String S4 = “ test”;

大部分情况下StringBuilder的速度要大于StringBuffer:

java.lang.StringBuilder一个可变的字符序列是5.0新增的。(大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的)此类提供一个与 StringBuffer兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer 要快。两者的方法基本相同。

对于三者使用的总结:

1)如果要操作少量的数据用 = String

2)单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

3)多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

5、面试题的回答

StringBuilder 与StringBuffer的区别,StringBuilder与String的区别。

  • String:不可变的字符序列;底层使用char[]存储
  • StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
  • StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全,效率高;底层使用char[]存储
  • 如果是简单的声明一个字符串没有后续过多的操作,使用String,StringBuilder均可;若后续对字符串做频繁的添加、删除操作,或者是在循环当中动态的改变字符穿的长度应该用StringBuilder。使用String会产生多余的字符串,占用内存空间。

Java比较器

  • 在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
  • Java实现对象排序的方式有两种:

    • 自然排序:java.lang.Comparable

    • 定制排序:java.util.Comparator

1、自然排序:Comparable接口的使用

  • 像String、包装类等实现了Comparable接口,重写了comparableTo(obj)方法,给出了比较两个对象大小的方法。
  • 像String、包装类等重写了comparableTo(obj)方法以后,进行了从小到达的排列。
  • 重写comparableTo(obj)方法的规则:

    • 如果当前对象this大于形参对象obj,则返回正整数;

    • 如果当前对象this小于形参对象obj,则返回负整数;

    • 如果当前对象this等于形参对象obj,则返回零。

  • 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法,在重写的compareTo(obj)方法中指明如何排序。

2、定制排序:java.util.Comparator

  • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序,强行对多个对象进行整体排序的比较。
  • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:

    • 如果方法返回正整数,则表示o1大于o2;

    • 如果方法返回负整数,则表示o1小于o2;

    • 如果方法返回正零,则表示o1等于o2。

  • 可以将Comparator传递给sort方法(如Collections.sort或者Arrays.sort),从而允许在排序顺序上实现精确控制。
  • 还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
public class compareTest {

    @Test
  public void test(){
    String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
    Arrays.sort(arr,new Comparator(){
      
      //按照字符串从大到小的顺序排列
      @Override
      public int compare(Object o1,Object o2){
        if(o1 instanceof String && o2 instanceof String){
          String s1 = (String) o1;
          String s2 = (String) o2;
          return -s1.compareTo(s2);
        }
        throw new RuntimeException("输入的数据类型不一致");
      }
    });
    System.out.println(Arrays.toString(arr));
  }
}
复制代码

Comparable 接口与Comparator使用的对比:

  • Comparable接口的方式一旦设定,保证Comparable接口实现类的对象在任何位置都可以比较大小;
  • Comparator接口属于临时性的比较。

File类

File类的基本使用

java.io.File类:文件和目录路径名的抽象表示形式。

File类的常见构造方法:

public File(String pathname)
复制代码

以pathname为路径创建File对象,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。

  • File的静态属性String separator存储了当前系统的路径分隔符。
  • 通过File对象可以访问文件的属性。
public boolean canRead()
public boolean exists()
public boolean isFile()
public long lastModified()
public String getName()
public boolean canWrite()
public boolean isDirectory()
public boolean isHidden()
public long length()
public String getPath()
复制代码
  • 通过File对象创建空文件或目录(在该对象所指的文件或目录不存在的情况下)。
public boolean createNewFile()throws IOException
public boolean delete()
public boolean mkdir(), mkdirs()
复制代码
  • 常见构造器,方法

【演示】

import java.io.File;
import java.io.IOException;

public class TestFile {
  /**
* File文件类 1.代表文件 2.代表目录
*/
  public static void main(String[] args) {
    File f = new File("d:/src3/TestObject.java");
    File f2 = new File("d:/src3");
    File f3 = new File(f2, "TestFile.java");
    File f4 = new File(f2, "TestFile666.java");
    File f5 = new File("d:/src3/aa/bb/cc/dd");
    //f5.mkdirs();
    f5.delete();
    
    try {
      f4.createNewFile();
      System.out.println("文件创建成功!");
    } catch (IOException e) {
      e.printStackTrace();
    }
    if (f.isFile()) {
      System.out.println("是一个文件!");
    }
    if (f2.isDirectory()) {
      System.out.println("是一个目录!");
    }
    if (f3.isFile()) {
      System.out.println("是一个文件奥");
    }
  }
  
}
复制代码

枚举类与注解

枚举类

  • 类的对象只有有限个,确定的。举例如下:

    • 星期:Monday(星期一)、...、Sunday(星期天)

    • 性别:男、女

    • 支付方式:Cash(现金)、WeChatPay(微信支付)、AliPay(支付宝)、BankCard(银行卡)、CreditCard(信用卡)

    • 订单状态:Nonpayment(未付款)、Paid(已付款)、Delivered(已发货)、Return(退货)、Checked(已确认)、Fulfilled(已配货)

    • 线程状态:创建、就绪、运行、阻塞、死亡

  • 当需要定义一组常量时,强烈建议使用枚举类。
  • 如果枚举类中只有一个对象,则可以作为单例模式的实现方式。

1、如何让自定义枚举类

  • 方式一:jdk5.0之前,自定义枚举类
public class SeasonTest(){
  
}
//自定义枚举类
class Season{
  //1.声明Season对象的属性: private final 修饰
  private final String seasonName;
  private final String seasonDesc;
  
  //2.私有化类的构造器,并给对象属性赋值
  private Season(String seasonName,String seasonDesc){
    this.seasonName = seasonName;
    this.seasonDesc = seasonDesc;
  }
  
  //3.提供当前枚举类的多个对象:public static final
  public static final Season SPRING = new Season("春天","春暖花开");
  public static final Season SUMMER = new Season("夏天","夏日炎炎");
  public static final Season AUTUMN = new Season("秋天","秋高气爽");
  public static final Season WINTER = new Season("冬天","冰天雪地");
  
  //4.其他诉求1:获取枚举类对象的属性
  public String getSeasonName(){
    return seasonName;
  }
  
  public String getSeasonDesc(){
    return seasonDesc;
  }
  
  //4.其他诉求2:提供toString()方法
  @Override
  public String toString(){
    return "Season{" +
      			"seasonName='" + seasonName + ''' +
      			",seasonDesc='" + seasonDesc + ''' +
      			'}';
  } 
  
}
复制代码

2、如何使用关键字enum定义枚举类

  • 方式二:jdk5.0,使用enum关键字定义枚举类
//使用enum关键字定义枚举类
enum Season{
  //1.提供当前枚举类的对象,多个对象之间使用“,”隔开,末尾对象使用“;”结束
  SPRING("春天","春暖花开"),
  SUMMER("夏天","夏日炎炎"),
  AUTUMN("秋天","秋高气爽"),
  WINTER("冬天","冰天雪地");
  
  //2.私有化类的构造器,并给对象属性赋值
  private Season(String seasonName,String seasonDesc){
    this.seasonName = seasonName;
    this.seasonDesc = seasonDesc;
  }
  
  //3.其他诉求1:获取枚举类对象的属性
  public String getSeasonName(){
    return seasonName;
  }
  
  public String getSeasonDesc(){
    return seasonDesc;
  }
  
}
复制代码
  • 使用enum定义的枚举类默认继承与 java.lang.Enum类

3、Enum类的主要方法

  • values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  • valueOf(String str)方法:返回枚举类中对象名时str的对象。要求字符串必须是枚举类对象。如果没有str名字的枚举类对象,则抛出异常:IllegalArgumentException
  • toString()方法:返回当前枚举类对象常量的名称。

4、使用enum关键字定义的枚举类实现接口的情况

  • 情况一:实现接口,在enum类中实现抽象方法
  • 情况二:让枚举类的对象分别实现接口中的方法

注解(Annotation)

  • 注解(Annotation)概述
  • 常见的Annotation实例
  • 自定义Annotation
  • JDK中的元注解
  • 利用反射获取注解信息
  • JDK8中注解的新特性

1、注解的概述

  • 从JDK5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注解)。
  • Annotation其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
  • Annotation可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在Annotation的“ name = value ”对中。
  • 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
  • 未来的开发模式就是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式

2、常见的Annotation示例

  • 使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素
    • 示例一:生成文档相关的注解
@author  标明开发该类模块的作者,多个作者之间使用“,”分割
@version  标明该类模块的版本
@see  参考转向,也就是相关主题
@since  从哪个版本开始增加的
@param  对方法中的某参数的说明,如果没有参数就不能写
@return  对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception  对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写
  
  其中
  @param @return @exception 这三个标记都是只用于方法的。
  @param 的格式要求:@param 形参名 形参类型 形参说明
  @return 的格式要求:@return 返回值类型 返回值说明
  @exception 的格式要求:@exception 异常类型 异常说明
  @param 和 @exception 可以并列多个
复制代码
  • 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override  限定重写父类方法,该注解只能用于方法
@Deprecated  用于表示所修饰的元素(类、方法等)已过时,通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings  抑制编译器警告
复制代码
  • 示例三:跟踪代码依赖性,实现替代配置文件功能

    • Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。
    • Spring框架中关于“事务”的管理。

3、自定义注解

  • 自定义新的Annotation类型使用 @interface 关键字
  • 自定义注解自动继承了 java.lang.annotation.Ab=nnotation接口
  • Annotation的成员变量在Annotation定义中以无参数方法的形式来声明,其方法名和返回值定义了该成员的名字和类型,我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
  • 可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用 default关键字
  • 如果只有一个参数成员,建议使用 参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“ 参数名 = 参数值 ”,如果只有一个参数成员,且名称为value,可以省略“ value = ”。
  • 没有成员定义的Annotation称为 标记,包含成员变量的Annotation称为元数据Annotation

注意:自定义注解必须配上注解的信息处理流程(使用反射)才有意义

public @interface MyAnnotation{
  String value() default "hello";
}

@MyAnnotation(value = "hi")
复制代码

4、JDK中的元注解

  • 元注解:对现有的注解进行解释说明的注解
  • JDK的元注解用于修饰其他的Annotation定义
  • JDK5.0提供了4个标准的 meta-annotation类型,分别是:

    • @Retention
      只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Retention 包含一个 RetentionPolicy 类型的成员变量,使用 @Retention 时必须为该value成员变量指定:

      • RetentionPolicy.SOURCE  在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释。
      • RetentionPolicy.CLASS  在class文件中有效(即class保留),当运行Java程序时,JVM不会保留注释,这是默认值。
      • RetentionPolicy.RUNTIME  在运行时有效(即运行时保留),当运行Java程序时,JVM会保留注释,程序可以通过反射获取该注释。
    • @Target
      用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target 也包含一个名为value的成员变量。

      • CONSTRUCTOR  用于描述构造器
      • FIELD  用于描述域
      • LOCAL_VARIABLE  用于描述局部变量
      • METHOD  用于描述方法
      • PACKAGE  用于描述包
      • PARAMETER  用于描述参数
      • TYPE  用于描述类、接口(包括注解类型)或enum声明的枚举类

自定义注解通常都会指明两个元注解:Retention、Target

  • @Documented
    用于指定被该元注解修饰的Annotation类将被javadoc工具提取成文档,表示所修饰的注解在被javadoc解析时,被保留下来。默认情况下,javadoc是不包括注解的。

    • 定义为 Documented 的注解必须设置 Retention 值为 RUNTIME。

    • @Inherited\

    被它修饰的Annotation将具有继承性。如果某个类使用了被 @Inherited 修饰的Annotation,则其子类将自动具有该注解。

    • 比如:如果把标有 @Inherited 注解的自定义注解标注在类级别上,子类则可以继承父类类级别的注解。

    • 实际应用中,使用较少。

元数据的理解:对现有数据进行修饰的数据

String name = "atguigu";

JDK8注解的新特性

  • 可重复注解

    • 在MyAnnotation上声明@Repeatable,成员值为 MyAnnotation.class

    • MyAnnotation 的 Target 和 Retention 和 MyAnnotations 相同

  • 类型注解

    • JDK1.8之后,关于元注解@Target的参数类型ElementType枚举类值多了两个:TYPE_PARAMETER,TYPE_USE

    • 在Java8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。

      • ElementType.TYPE_PARAMETER  表示该注解能写在类型变量的声明语句中(如:泛型声明)

      • ElementType.TYPE_USE  表示该注解能写在使用类型的任何语句中

猜你喜欢

转载自juejin.im/post/7087411215147728909