我的个人博客

哪有什么胜利可言,挺着就代表着一切


  • 首页

  • 归档

菜鸟话Java---java继承

发表于 2019-03-22 | 分类于 java

类,超类和子类

判断继承关系

如果类和另一个类之间存在is-a(什么是什么)关系,比如狗是动物,金鱼是鱼等。那么就可以使用继承

定义子类

关键字extends表示正在构造的子类继承(或派生)于一个以存在的类。这个以存在的类叫做超类(常用),基类或父类;新类
称为子类(常用),派生类和孩子类。例如:

格式:

1
2
3
4
5
class 父类 {
}

class 子类 extends 父类 {
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Animal {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void eat(){
System.out.println(name+"正在吃东西");
}

}

public class Dog extends Animal {
private String master;//狗的主人

public String getMaster() {
return master;
}

public void setMaster(String master) {
this.master = master;
}
}

public static void main(String[] args) {
Dog dog = new Dog();
dog.setName("阿福");
dog.setMaster("小明");
dog.eat();
}

从示例可以看到尽管Dog类没有显式地定义setName()和getName()等方法,但是属于Animal的类
却可以使用它们,这是因为Dog自动继承了超类Animal中这些方法(只能继承被public和protect修饰的字段)。

子类具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,
不用再多次写同样的代码)

注意:Java只支持单继承

覆盖方法

如果我们对父类中已有的方法不满意,就可以自己覆盖父类的方法。例如:

1
2
3
4
5
6
7
8
public class Dog extends Animal {
...

@Override
public void eat() {//覆盖父类的方法,其他不变;程序执行结果为:小明给阿福吃牛排
System.out.println(master+"给"+getName()+"吃牛排");
}
}

注意:在覆盖一个方法时,子类的方法不能低于超类的可见性。特别是,如果超类的方法为public,子类方法
一定要声明为public;覆盖时的返回值可以从父类型改为对应的子类型。

super

super是一个指示编译器调用超类方法的特殊关键字。示例:

1
2
3
4
5
6
7
8
public class Dog extends Animal {
...

@Override
public String getName() {
return "小狗名字是:"+super.getName();//super.getName()调用父类Animal的方法
}
}

子类构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package five;

public class Animal {

...

//给Animal加一个的构造器
public Animal(String name) {
this.name = name;
}
}

public class Dog extends Animal {

...

//子类构造器
public Dog(String name,String master) {
super(name);
this.master = master;
}
}

由于Dog类的构造器不能访问Animal的私有域,所有必须利用Animal类的构造器对这部分私有域进行
初始化,我们可以通过super关键字实现对超类构造器的调用。使用super调用构造器的语句必须是子类
构造器的第一条语句

如果子类构造器没有显式地调用超类的构造器,则自动地调用超类默认的(不带参数的)构造器。如果超类没有默认
的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则Java编译器报告错误。

多态

转载菜鸟教程

在Java中,对象变量是多态的,一个Animal变量既可以引用一个Dog类对象,也可以引用一个Animal类的任何
一个子类的对象。

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
以下是一个多态实例的演示,详细说明请看注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法

Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}

public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}

abstract class Animal {
abstract void eat();
}

class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}

class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
/*
执行以上程序,输出结果为:
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
*/

注意:不能将一个超类的引用赋给子类变量;但是在Java中,子类数组的引用可以转为超类数组的引用,而不需要采用强制类型转换

继承中方法的调用


注意:上面流程图中判断方法类型中判断private方法是在dog.setName()方法中可能调用了private方法,调用这个private方法时要重新走这个流程所以要判断。

每次调用方法都要进行搜索,时间开销大。因此,虚拟机预先为每一个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法。这样一来
,在真正调用这个方法时,虚拟机只查这个表就行。

final类和方法

为类加上final,该类就无法被继承;为方法加上final,该方法就无法被覆写

instanceof

instanceof是用来在运行时指出对象是否是特定类的一个实例,instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例

1
2
3
if(dog instanceof Animal){//如果 dog = null,会返回false
dog.eat();
}

抽象类

转载菜鸟教程

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象类的定义

在Java语言中使用abstract class来定义抽象类。如下实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 文件名 : Employee.java */
public abstract class Employee
{
private String name;
private String address;
private int number;
public Employee(String name, String address, int number)
{
System.out.println("Constructing an Employee");
this.name = name;
this.address = address;
this.number = number;
}
public double computePay()
{
System.out.println("Inside Employee computePay");
return 0.0;
}
public void mailCheck()
{
System.out.println("Mailing a check to " + this.name
+ " " + this.address);
}
public String toString()
{
return name + " " + address + " " + number;
}
public String getName()
{
return name;
}
public String getAddress()
{
return address;
}
public void setAddress(String newAddress)
{
address = newAddress;
}
public int getNumber()
{
return number;
}
}

注意到该 Employee 类没有什么不同,尽管该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 现在如果你尝试如下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
/* 文件名 : AbstractDemo.java */
public class AbstractDemo
{
public static void main(String [] args)
{
/* 以下是不允许的,会引发错误 */
Employee e = new Employee("George W.", "Houston, TX", 43);

System.out.println("\n Call mailCheck using Employee reference--");
e.mailCheck();
}
}

当你尝试编译AbstractDemo类时,会产生如下错误:

1
2
3
4
Employee.java:46: Employee is abstract; cannot be instantiated
Employee e = new Employee("George W.", "Houston, TX", 43);
^
1 error

继承抽象类

我们能通过一般的方法继承Employee类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* 文件名 : Salary.java */
public class Salary extends Employee
{
private double salary; //Annual salary
public Salary(String name, String address, int number, double
salary)
{
super(name, address, number);
setSalary(salary);
}
public void mailCheck()
{
System.out.println("Within mailCheck of Salary class ");
System.out.println("Mailing check to " + getName()
+ " with salary " + salary);
}
public double getSalary()
{
return salary;
}
public void setSalary(double newSalary)
{
if(newSalary >= 0.0)
{
salary = newSalary;
}
}
public double computePay()
{
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
}

尽管我们不能实例化一个 Employee 类的对象,但是如果我们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 文件名 : AbstractDemo.java */
public class AbstractDemo
{
public static void main(String [] args)
{
Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);

System.out.println("Call mailCheck using Salary reference --");
s.mailCheck();

System.out.println("\n Call mailCheck using Employee reference--");
e.mailCheck();
}
}

以上程序编译运行结果如下:

1
2
3
4
5
6
7
8
9
Constructing an Employee
Constructing an Employee
Call mailCheck using Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0

Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

1
2
3
4
5
6
7
8
9
10
public abstract class Employee
{
private String name;
private String address;
private int number;

public abstract double computePay();

//其余代码
}

声明抽象方法会造成以下两个结果:
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
如果Salary类继承了Employee类,那么它必须实现computePay()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 文件名 : Salary.java */
public class Salary extends Employee
{
private double salary; // Annual salary

public double computePay()
{
System.out.println("Computing salary pay for " + getName());
return salary/52;
}

//其余代码
}

抽象类总结规定

  1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
  4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
  5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类

protected

protected修饰的方法和属性对本包和所有子类可见。在实际应用中,要谨慎使用protected属性。

Object

Object类是Java中所有类(不包括基本数据类型)的超类,在Java中每个类都是由它扩展而来的。如果没有明确指出超类,Object就被
认为是这个类的超类,所以不需要这样显式声明

1
2
3
public Animal extends Object{

}

所有的数组类型,不管是对象数组还是基本类型的数组都扩展Object类

菜鸟话Java---System的使用

发表于 2019-03-21 | 分类于 java

转载这里

System 类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于 java. lang 包。由于该类的构造方法是 private 的,所以无法创建该类的对象,也就是无法实例化该类。

System 类内部的成员变量和成员方法都是 static 的,所以可以方便地进行调用。
System 类的成员变量
System 类有 3 个静态成员变量,分别是 PrintStream out、InputStream in 和 PrintStream err。

PrintStream out

标准输出流。此流已打开并准备接收输出数据。通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。
例如,编写一行输出数据的典型方式是

1
System.out.println(data);

其中,println 方法是属于流类 PrintStream 的方法,而不是 System 中的方法。

InputStream in

标准输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。

PrintStream err

标准的错误输出流。其语法与 System.out 类似,不需要提供参数就可输出错误信息。也可以用来输出用户指定的其他信息,包括变量的值。
例 1
编写一个 Java 程序,使用本节介绍的 System 类实现从键盘输入字符并显示出来。 具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.IOException;
public class Test06
{
public static void main(String[] args)
{
System.out.println("请输入字符,按回车键结束输入:");
int c;
try
{
c=System.in.read(); //读取输入的字符
while(c!='\r')
{ //判断输入的字符是不是回车
System.out.print((char) c); //输出字符
c=System.in.read();
}
}
catch(IOException e)
{
System.out.println(e.toString());
}
finally
{
System.err.println();
}
}
}

以上代码中,System.in.read() 语句读入一个字符,read() 方法是 InputStream 类拥有的方法。变量 c 必须用 int 类型而不能用 char 类型,否则会因为丢失精度而导致编译失败。

以上的程序如果输入汉字将不能正常输出。如果要正常输出汉字,需要把 System.in 声明为 InputStreamReader 类型的实例,最终在 try 语句块中的代码为

1
2
3
4
5
6
7
InputStreamReader in=new InputStreamReader(System.in);
c=in.read();
while(c!='\r')
{
System.out.print((char) c);
c=in.read();
}

System 类的成员方法

System 类中提供了一些系统级的操作方法,常用的方法有 arraycopy()、currentTimeMillis()、exit()、gc() 和 getProperty()。

  1. arraycopy() 方法

该方法的作用是数组复制,即从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。该方法的具体定义如下:
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
其中,src 表示源数组,srcPos 表示从源数组中复制的起始位置,dest 表示目标数组,destPos 表示要复制到的目标数组的起始位置,length 表示复制的个数。
例 2
下面的示例代码演示了 arraycopy() 方法的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class System_arrayCopy
{
public static void main(String[] args)
{
char[] srcArray={'A','B','C','D'};
char[] destArray={'E','F','G','H'};
System.arraycopy(srcArray,1,destArray,1,2);
System.out.println("源数组:");
for(int i=0;i<srcArray.length;i++)
{
System.out.println(srcArray[i]);
}
System.out.println("目标数组:");
for(int j=0;j<destArray.length;j++)
{
System.out.println(destArray[j]);
}
}
}

如上述代码,将数组 srcArray 中,从下标 1 开始的数据复制到数组 destArray 从下标 1 开始的位置,总共复制两个。也就是将 srcArray1 复制给 destArray1,将 srcArray[2] 复制给 destArray[2]。这样经过复制之后,数组 srcArray 中的元素不发生变化,而数组 destArray 中的元素将变为 E、B、C、 H,下面为输出结果:

1
2
3
4
5
6
7
8
9
10
源数组:
A
B
C
D
目标数组:
E
B
C
H
  1. currentTimeMillis() 方法

该方法的作用是返回当前的计算机时间,时间的格式为当前计算机时间与 GMT 时间(格林尼治时间)1970 年 1 月 1 日 0 时 0 分 0 秒所差的毫秒数,例如:
long m=System.currentTimeMillis();
上述语句将获得一个长整型的数字,该数字就是以差值表达的当前时间。

例 3
使用 currentTimeMillis() 方法来显示时间不够直观,但是可以很方便地进行时间计算。例如,计算程序运行需要的时间就可以使用如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class System_currentTimeMillis
{
public static void main(String[] args)
{
long start=System.currentTimeMillis();
for(int i=0;i<100000000;i++)
{
int temp=0;
}
long end=System.currentTimeMillis();
long time=end-start;
System.out.println("程序执行时间"+time+"秒");
}
}

上述代码中的变量 time 的值表示代码中 for 循环执行所需要的毫秒数,使用这种方法可以测试不同算法的程序的执行效率高低,也可以用于后期线程控制时的精确延时实现。

  1. exit() 方法

该方法的作用是终止当前正在运行的 Java 虚拟机,具体的定义格式如下:
public static void exit(int status)
其中,status 的值为 0 时表示正常退出,非零时表示异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。

  1. gc() 方法

该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,取决于系统中垃圾回收算法的实现以及系统执行时的情况。定义如下:
public static void gc()

  1. getProperty() 方法

该方法的作用是获得系统中属性名为 key 的属性对应的值,具体的定义如下:
public static String getProperty(String key)

系统中常见的属性名以及属性的说明如表 1 所示。
表1 系统常见属性

属性名 属性说明
java. version Java 运行时琢境版本
java.home Java 安装目录
os.name 操作系统的名称
os.version 操作系统的版本
user.name 用户的账户名称
user.home 用户的主目录
user.dir 用户的当前工作目录

例 4
下面的示例演示了 getProperty() 方法的使用。

1
2
3
4
5
6
7
8
9
10
11
12
public class System_getProperty
{
public static void main(String[] args)
{
String jversion=System.getProperty("java.version");
String oName=System.getProperty("os.name");
String user=System.getProperty("user.name");
System.out.println("Java 运行时环境版本:"+jversion);
System.out.println("当前操作系统是:"+oName);
System.out.println("当前用户是:"+user);
}
}
1
2
3
4
运行该程序,输出的结果如下: 
Java 运行时环境版本:1.6.0_26
当前操作系统是:Windows 7
当前用户是:Administrator

提示:使用 getProperty() 方法可以获得很多系统级的参数以及对应的值,这里不再一一举例。

菜鸟话Java---Math类详解

发表于 2019-03-21 | 分类于 java

转载这里

Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。
Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。下面是Math的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Demo{
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的随机数
*/
System.out.println(Math.random()); //小于1大于0的double类型的数
System.out.println(Math.random()*2);//大于0小于1的double类型的数
System.out.println(Math.random()*2+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
}
}

Gradle教程

发表于 2019-03-19 | 分类于 Gradle

java源码分析---String

发表于 2019-03-19 | 分类于 java源码分析

String的定义

1
2
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence

由上面String的定义可知:

  • String被final修饰,所以不能被继承
  • String继承Comparable接口,可以使用Arrays.sort就行排序

String的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**用来储存字符串,可见String的底层是数组实现的 */
private final char value[];

/**哈希值,默认是0 */
private int hash;

/** 实现序列化的标识 */
private static final long serialVersionUID = -6849794470754667710L;

/**
*声明序列化时要包含的域,具体的可以看 序列化和如何反序列化
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];

String的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
*无参构造方法,默认值是""
*/
public String() {
this.value = "".value;
}

/**
*直接引用传递来字符串的数组,给hash赋传递过来String的hash值
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

/**
*拷贝参数数组,为了防止外部对String内部的破坏
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

/**
*根据要求拷贝数组
*/
public String(char value[], int offset, int count) {
if (offset < 0) {//起始值要大于0
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {//count不能小于0
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {//当count = 0,offset<=value.length时,字符串为""
this.value = "".value;
return;
}
}
//这里判断 offset + count是否超过要拷贝的数组长度
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}

/**
*和上面的构造方法的作用相同,不过参数数组是字符对应的Unicode代码点
*/
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}

if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}

final int end = offset + count;
// 计算char []的精确大小
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))//判断判断指定字符的Unicode编码是否在BMP中,根据这个结果可以知道代码点是否能被单字符表示。
continue;
else if (Character.isValidCodePoint(c))//判断指定字符的Unicode编码是否是有效的
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}

// 分配并填写char []
final char[] v = new char[n];

for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);//具体的处理增补码
}

this.value = v;
}

/**
*已废弃
*/
@Deprecated
public String(byte ascii[], int hibyte, int offset, int count) {
....
}

/**
*已废弃
*/
@Deprecated
public String(byte ascii[], int hibyte) {
....
}

/**
*根据指定数组,起始位置,偏移量,字符编码来给value赋值
*/
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);//判断边界
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

/**
*和上面的构造方法相同,这里是使用 Charset,而不是直接用String来指定编码格式
*/
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}

/**
*调用String(byte bytes[], int offset, int length, String charsetName)来构造字符串
*/
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}

/**
*调用String(byte bytes[], int offset, int length, Charset charset)来构造字符串
*/
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}

/**
*和上面的构造方法类似,只是这里使用默认的编码
*/
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}

/**
*调用上面的构造方法
*/
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}

/**
*使用StringBuffer构造String
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}

/**
*使用StringBuilder构造String
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

/*
*只对包内可见的构造方法,第二个参数只用来重载,
*这里直接给value赋值,更加高效
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}

String方法源码分析

length()

1
2
3
public int length() {
return value.length;//返回数组的大小,作为字符串的长度
}

isEmpty()

1
2
3
public boolean isEmpty() {
return value.length == 0;//通过判断value的大小是否为0来判断字符串是否为空
}

charAt(int index)

1
2
3
4
5
6
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {//判断参数是否越界
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}

codePointAt类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);//返回String对象的char数组index位置的元素的码元

}

//返回index位置元素的前一个元素的码元
public int codePointBefore(int index) {
int i = index - 1;
if ((i < 0) || (i >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}

codePointCount(int beginIndex, int endIndex)

1
2
3
4
5
6
7
//与length()方法类似
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}

length()方法返回的是使用的是UTF-16编码的字符代码单元数量,不一定是实际上我们认为的字符个数。codePointCount()方法返回的是代码点个数,是实际上的字符个数

例如:

String str = “/uD835/uDD6B”,那么机器会识别它是2个代码单元代理的1个代码点”Z“,故而,length的结果是代码单元数量2,而codePointCount()的结果是代码点数量1.

offsetByCodePoints(int index, int codePointOffset)

1
2
3
4
5
6
7
8
9
10
11
12
   /**
* 也是相对Unicode字符集而言的,从index索引位置算起,偏移codePointOffset个位置,返回偏移后的位置是多少
* 例如,index = 2 ,codePointOffset = 3 ,maybe返回 5
*/

public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > value.length) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePointsImpl(value, 0, value.length,
index, codePointOffset);
}

getChars

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 把String中的字符数组拷贝到目标数组中,dstBegin是目标数组开始拷贝的起始位置;这个方法不对外开放
*,不进行安全检查
*/
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}

/**
*srcBegin是源数组的起始位置,srcEnd是结束位置,dstBegin是目标数组开始拷贝的起始位置
*进行安全检查
*/
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

/**
*过时的方法
*/
@Deprecated
public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) {
....
}

/**
*根据指定编码获取字符串的byte[]数组
*/
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}

/**
*根据指定编码获取字符串的byte[]数组
*/
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}

/**
*根据默认编码获取字符串的byte[]数组
*/
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}

equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
   /**
*判断两个字符串是否相等
*/
public boolean equals(Object anObject) {
if (this == anObject) {//判断是否为同一对象
return true;
}
if (anObject instanceof String) {//判断anObject是否为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;
}

/**
* 这也是一个String的equals方法,与上一个方法不用,该方法(不区分大小写),从名字也能看出来
* 是对String的equals方法的补充。
* 这里参数这是一个String对象,而不是Object了,因为这是String本身的方法,不是重写谁的方法
*/
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true //一样,先判断是否为同一个对象
: (anotherString != null)
&& (anotherString.value.length == value.length) //再判断长度是否相等
&& regionMatches(true, 0, anotherString, 0, value.length); //再执行regionMatchs方法
}


/**
* 这是一个公有的比较方法,参数是StringBuffer类型
* 实际调用的是contentEquals(CharSequence cs)方法,可以说是StringBuffer的特供版
*/
public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}

/**
* 这是一个私有方法,特供给比较StringBuffer和StringBuilder使用的。
* 比如在contentEquals方法中使用,参数是AbstractStringBuilder抽象类的子类
*
*/
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
char v1[] = value; //当前String对象的值
char v2[] = sb.getValue(); //AbstractStringBuilder子类对象的值
int n = v1.length; //后面就不说了,其实跟equals方法是一样的,只是少了一些判断
if (n != sb.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
return true;
}

/**
* 这是一个常用于String对象跟StringBuffer和StringBuilder比较的方法
* 参数是StringBuffer或StringBuilder或String或CharSequence
* StringBuffer和StringBuilder和String都实现了CharSequence接口
*/
public boolean contentEquals(CharSequence cs) {
// Argument is a StringBuffer, StringBuilder
if (cs instanceof AbstractStringBuilder) { //如果是AbstractStringBuilder抽象类或其子类
if (cs instanceof StringBuffer) { //如果是StringBuffer类型,进入同步块
synchronized(cs) {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else { //如果是StringBuilder类型,则进入非同步块
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}

/***下面就是String和CharSequence类型的比较算法*****/
// Argument is a String
if (cs instanceof String) {
return equals(cs);
}
char v1[] = value;
int n = v1.length;
if (n != cs.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != cs.charAt(i)) {
return false;
}
}
return true;
}

regionMatches

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
 /**
* 这是一个类似于equals的方法,比较的是字符串的片段,也即是部分区域的比较
* toffset是当前字符串的比较起始位置(偏移量),other是要比较的String对象参数,ooffset是要参数String的比较片段起始位置,len是两个字符串要比较的片段的长度大小
*
* 例子:String str1 = "0123456",Str2 = "0123456789";
* str1.regionMatchs(0,str2,0,6);意思是str1从0位置开始于str2的0位置开始比较6个长度的字符串片段
* 相等则返回 true,不等返回false
*/
public boolean regionMatches(int toffset, String other, int ooffset,
int len) {
char ta[] = value; //当前对象的值
int to = toffset; //当前对象的比较片段的起始位置,既偏移量
char pa[] = other.value; //参数,既比较字符串的值
int po = ooffset; //比较字符串的起始位置
if ((ooffset < 0) || (toffset < 0) //起始位置不小于0或起始位置不大于字符串长度 - 片段长度,大于就截取不到这么长的片段了
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false; //惊讶脸,居然不是抛异常,而是返回false
}
while (len-- > 0) { //使用while循环,当然也可以使for循环
if (ta[to++] != pa[po++]) { //片段区域的字符元素逐个比较
return false;
}
}
return true;
}

/**
* 这个跟上面的方法一样,只不过多了一个参数,既ignoreCase,既是否为区分大小写。
* 是equalsIgnoreCase()方法的片段比较版本,实际上equalsIgnoreCase()也是调用regionMatches函数
*/
public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
//上面的解释同上
while (len-- > 0) {
char c1 = ta[to++];
char c2 = pa[po++];
if (c1 == c2) {
continue;
}
if (ignoreCase) { //当ignoreCase为true时,既忽视大小写时
// If characters don't match but case may be ignored,
// try converting both characters to uppercase.
// If the results match, then the comparison scan should
// continue.
char u1 = Character.toUpperCase(c1); //片段中每个字符转换为大写
char u2 = Character.toUpperCase(c2);
if (u1 == u2) { //大写比较一次,如果相等则不执行下面的语句,进入下一个循环
continue;
}
// Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before
// exiting.
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
//每个字符换行成小写比较一次
continue;
}
}
return false;
}
return true;
}

compareTo类函数和CaseInsensitiveComparator静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 这是一个比较字符串中字符大小的函数,因为String实现了Comparable<String>接口,所以重写了compareTo方法
* Comparable是排序接口。若一个类实现了Comparable接口,就意味着该类支持排序。
* 实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
*
* 参数是需要比较的另一个String对象
* 返回的int类型,正数为大,负数为小,是基于字符的ASSIC码比较的
*
*/
public int compareTo(String anotherString) {
int len1 = value.length; //当前对象的长度
int len2 = anotherString.value.length; //比较对象的长度
int lim = Math.min(len1, len2); //获得最小长度
char v1[] = value; //获得当前对象的值
char v2[] = anotherString.value; //获得比较对象的值

int k = 0; //相当于for的int k = 0,就是为while循环的数组服务的
while (k < lim) { //当当前索引小于两个字符串中较短字符串的长度时,循环继续
char c1 = v1[k]; //获得当前对象的字符
char c2 = v2[k]; //获得比较对象的字符
if (c1 != c2) { //从前向后遍历,只要其实一个不相等,返回字符ASSIC的差值,int类型
return c1 - c2;
}
k++;
}
return len1 - len2; //如果两个字符串同样位置的索引都相等,返回长度差值,完全相等则为0
}

/**
* 这时一个类似compareTo功能的方法,但是不是comparable接口的方法,是String本身的方法
* 使用途径,我目前只知道可以用来不区分大小写的比较大小,但是不知道如何让它被工具类Collections和Arrays运用
*
*/
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}

/**
* 这是一个饿汉单例模式,是String类型的一个不区分大小写的比较器
* 提供给Collections和Arrays的sort方法使用
* 例如:Arrays.sort(strs,String.CASE_INSENSITIVE_ORDER);
* 效果就是会将strs字符串数组中的字符串对象进行忽视大小写的排序
*
*/
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();

/**
* 这一个私有的静态内部类,只允许String类本身调用
* 实现了序列化接口和比较器接口,comparable接口和comparator是有区别的
* 重写了compare方法,该静态内部类实际就是一个String类的比较器
*
*/
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {
int n1 = s1.length(); //s1字符串的长度
int n2 = s2.length(); //s2字符串的长度
int min = Math.min(n1, n2); //获得最小长度
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i); //逐一获得字符串i位置的字符
char c2 = s2.charAt(i);
if (c1 != c2) { //部分大小写比较一次
c1 = Character.toUpperCase(c1); //转换大写比较一次
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1); //转换小写比较一次
c2 = Character.toLowerCase(c2);
if (c1 != c2) { //返回字符差值
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2; //如果字符相等,但是长度不等,则返回长度差值,短的教小,所以小-大为负数
}

/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}

以上的代码可以看出:

以上的最大问题可以能就是为什么要有个静态内部类,为什么实现了compareTo又有compare,移步到下面,有解答
String实现了comparable接口,重写了compareTo方法,可以用于自己写类进行判断排序,也可以使用collections,Arrays工具类的sort进行排序。只有集合或数组中的元素实现了comparable接口,并重写了compareTo才能使用工具类排序。

CASE_INSENSITIVE_ORDER是一个单例,是String提供为外部的比较器,该比较器的作用是忽视大小写进行比较,我们可以通过Collections或Arrays的sort方法将CASE_INSENSITIVE_ORDER比较器作为参数传入,进行排序。

startWith、endWith类函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 作用就是当前对象[toffset,toffset + prefix.value.lenght]区间的字符串片段等于prefix
* 也可以说当前对象的toffset位置开始是否以prefix作为前缀
* prefix是需要判断的前缀字符串,toffset是当前对象的判断起始位置
*/
public boolean startsWith(String prefix, int toffset) {
char ta[] = value; //获得当前对象的值
int to = toffset; //获得需要判断的起始位置,偏移量
char pa[] = prefix.value; //获得前缀字符串的值
int po = 0;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > value.length - pc)) { //偏移量不能小于0且能截取pc个长度
return false; //不能则返回false
}
while (--pc >= 0) { //循环pc次,既prefix的长度
if (ta[to++] != pa[po++]) { //每次比较当前对象的字符串的字符是否跟prefix一样
return false; //一样则pc--,to++,po++,有一个不同则返回false
}
}
return true; //没有不一样则返回true,当前对象是以prefix在toffset位置做为开头
}

/**
* 判断当前字符串对象是否以字符串prefix起头
* 是返回true,否返回fasle
*/
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}

/**
* 判断当前字符串对象是否以字符串prefix结尾
* 是返回true,否返回fasle
*/
public boolean endsWith(String suffix) {
//suffix是需要判断是否为尾部的字符串。
//value.length - suffix.value.length是suffix在当前对象的起始位置
return startsWith(suffix, value.length - suffix.value.length);
}

所以我们知道:
endsWith的实现也是startWith(),作用就是判断前后缀

hashCode()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 这是String字符串重写了Object类的hashCode方法。
* 给由哈希表来实现的数据结构来使用,比如String对象要放入HashMap中。
* 如果没有重写HashCode,或HaseCode质量很差则会导致严重的后果,既不靠谱的后果
*
*/
public int hashCode() {
int h = hash; //hash是属性字段,是成员变量,所以默认为0
if (h == 0 && value.length > 0) { //如果hash为0,且字符串对象长度大于0,不为""
char val[] = value; //获得当前对象的值

//重点,String的哈希函数

for (int i = 0; i < value.length; i++) { //遍历len次
h = 31 * h + val[i]; //每次都是31 * 每次循环获得的h +第i个字符的ASSIC码
}
hash = h;
}
return h; //由此可见""空字符对象的哈希值为0
}

所以我们可以知道:

hashCode的重点就是哈希函数
String的哈希函数就是循环len次,每次循环体为 31 * 每次循环获得的hash + 第i次循环的字符

indexOf、lastIndexOf类函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/**
* 返回cn对应的字符在字符串中第一次出现的位置,从字符串的索引0位置开始遍历
*
*/
public int indexOf(int ch) {
return indexOf(ch, 0);
}

/**
* index方法就是返回ch字符第一次在字符串中出现的位置
* 既从fromIndex位置开始查找,从头向尾遍历,ch整数对应的字符在字符串中第一次出现的位置
* -1代表字符串没有这个字符,整数代表字符第一次出现在字符串的位置
*/
public int indexOf(int ch, int fromIndex) {
final int max = value.length; //获得字符串对象的长度
if (fromIndex < 0) { //如果偏移量小于0,则代表偏移量为0,校正偏移量
fromIndex = 0;
} else if (fromIndex >= max) { //如果偏移量大于最大长度,则返回-1,代表没有字符串没有ch对应的字符
// Note: fromIndex might be near -1>>>1.
return -1;
}

if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { //emmm,这个判断,不懂
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value; //获得字符串值
for (int i = fromIndex; i < max; i++) { //从fromIndex位置开始向后遍历
if (value[i] == ch) { //只有字符串中的某个位置的元素等于ch
return i; //返回对应的位置,函数结束,既第一次出现的位置
}
}
return -1; //如果没有出现,则返回-1
} else {
return indexOfSupplementary(ch, fromIndex); //emmm,紧紧接着没看懂的地方
}
}


private int indexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
final char hi = Character.highSurrogate(ch);
final char lo = Character.lowSurrogate(ch);
final int max = value.length - 1;
for (int i = fromIndex; i < max; i++) {
if (value[i] == hi && value[i + 1] == lo) {
return i;
}
}
}
return -1;
}

/**
* 从尾部向头部遍历,返回cn第一次出现的位置,value.length - 1就是起点
* 为了理解,我们可以认为是返回cn对应的字符在字符串中最后出现的位置
*
* ch是字符对应的整数
*/
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}

/**
* 从尾部向头部遍历,从fromIndex开始作为起点,返回ch对应字符第一次在字符串出现的位置
* 既从头向尾遍历,返回cn对应字符在字符串中最后出现的一次位置,fromIndex为结束点
*
*/
public int lastIndexOf(int ch, int fromIndex) {
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { //之后不解释了,emmmmmmm
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
//取最小值,作用就是校正,如果fromIndex传大了,就当时len - 1
int i = Math.min(fromIndex, value.length - 1);
for (; i >= 0; i--) { //算法中是从后向前遍历,直到i<0,退出循环
if (value[i] == ch) { //只有有相等,返回对应的索引位置
return i;
}
}
return -1; //没有找到则返回-1
} else {
return lastIndexOfSupplementary(ch, fromIndex);
}
}


private int lastIndexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
char hi = Character.highSurrogate(ch);
char lo = Character.lowSurrogate(ch);
int i = Math.min(fromIndex, value.length - 2);
for (; i >= 0; i--) {
if (value[i] == hi && value[i + 1] == lo) {
return i;
}
}
}
return -1;
}

/**
* 返回第一次出现的字符串的位置
*
*/
public int indexOf(String str) {
return indexOf(str, 0);
}

/**
*
* 从fromIndex开始遍历,返回第一次出现str字符串的位置
*
*/
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}

/**
* 这是一个不对外公开的静态函数
* source就是原始字符串,sourceOffset就是原始字符串的偏移量,起始位置。
* sourceCount就是原始字符串的长度,target就是要查找的字符串。
* fromIndex就是从原始字符串的第fromIndex开始遍历
*
*/
static int indexOf(char[] source, int sourceOffset, int sourceCount,
String target, int fromIndex) {
return indexOf(source, sourceOffset, sourceCount,
target.value, 0, target.value.length,
fromIndex);
}

/**
* 同是一个不对外公开的静态函数
* 比上更为强大。
* 多了一个targetOffset和targetCount,既代表别查找的字符串也可以被切割
*/
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) { //如果查找的起点大于当前对象的大小
//如果目标字符串的长度为0,则代表目标字符串为"",""在任何字符串都会出现
//配合fromIndex >= sourceCount,所以校正第一次出现在最尾部,仅仅是校正作用
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) { //也是校正,如果起始点小于0,则返回0
fromIndex = 0;
}
if (targetCount == 0) { //如果目标字符串长度为0,代表为"",则第一次出现在遍历起始点fromIndex
return fromIndex;
}

char first = target[targetOffset]; //目标字符串的第一个字符
int max = sourceOffset + (sourceCount - targetCount); //最大遍历次数

for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}

/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);

if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}

/**
* 查找字符串Str最后一次出现的位置
*/
public int lastIndexOf(String str) {
return lastIndexOf(str, value.length);
}


public int lastIndexOf(String str, int fromIndex) {
return lastIndexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}


static int lastIndexOf(char[] source, int sourceOffset, int sourceCount,
String target, int fromIndex) {
return lastIndexOf(source, sourceOffset, sourceCount,
target.value, 0, target.value.length,
fromIndex);
}


static int lastIndexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
/*
* Check arguments; return immediately where possible. For
* consistency, don't check for null str.
*/
int rightIndex = sourceCount - targetCount;
if (fromIndex < 0) {
return -1;
}
if (fromIndex > rightIndex) {
fromIndex = rightIndex;
}
/* Empty string always matches. */
if (targetCount == 0) {
return fromIndex;
}

int strLastIndex = targetOffset + targetCount - 1;
char strLastChar = target[strLastIndex];
int min = sourceOffset + targetCount - 1;
int i = min + fromIndex;

startSearchForLastChar:
while (true) {
while (i >= min && source[i] != strLastChar) {
i--;
}
if (i < min) {
return -1;
}
int j = i - 1;
int start = j - (targetCount - 1);
int k = strLastIndex - 1;

while (j > start) {
if (source[j--] != target[k--]) {
i--;
continue startSearchForLastChar;
}
}
return start - sourceOffset + 1;
}
}

从上可以看出:

只对外提供了int整形,String字符串两种参数的重载方法(虽然是Int型,其实我们就当做是传char也无所谓,因为虚拟机会帮我们解决这个事情的)

substring()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 截取当前字符串对象的片段,组成一个新的字符串对象
* beginIndex为截取的初始位置,默认截到len - 1位置
*/
public String substring(int beginIndex) {
if (beginIndex < 0) { //小于0抛异常
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex; //新字符串的长度
if (subLen < 0) { //小于0抛异常
throw new StringIndexOutOfBoundsException(subLen);
}
//如果beginIndex是0,则不用截取,返回自己(非新对象),否则截取0到subLen位置,不包括(subLen)
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

/**
* 截取一个区间范围
* [beginIndex,endIndex),不包括endIndex
*/
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}


public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}

从上面可以看到:

substring函数是一个不完全闭包的区间,是[beginIndex,end),不包括end位置
subString的原理是通过String的构造函数实现的

concat()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
	/**
* String的拼接函数
* 例如:String str = "abc"; str.concat("def") output: "abcdef"
*
*/
public String concat(String str) {
int otherLen = str.length();//获得参数字符串的长度
if (otherLen == 0) { //如果长度为0,则代表不需要拼接,因为str为""
return this;
}

/****重点****/

int len = value.length; //获得当前对象的长度
//将数组扩容,将value数组拷贝到buf数组中,长度为len + str.lenght
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len); //然后将str字符串从buf字符数组的len位置开始覆盖,得到一个完整的buf字符数组
return new String(buf, true);//构建新的String对象,调用私有的String构造方法
}

1234567891011121314151617181920

15、replace、replaceAll类函数
//替换,将字符串中的oldChar字符全部替换成newChar
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) { //如果旧字符不等于新字符的情况下
int len = value.length; //获得字符串长度
int i = -1; //flag
char[] val = value; /* avoid getfield opcode */

while (++i < len) { //循环len次
if (val[i] == oldChar) { //找到第一个旧字符,打断循环
break;
}
}
if (i < len) { //如果第一个旧字符的位置小于len
char buf[] = new char[len]; 新new一个字符数组,len个长度
for (int j = 0; j < i; j++) {
buf[j] = val[j]; 把旧字符的前面的字符都复制到新字符数组上
}
while (i < len) { //从i位置开始遍历
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c; //发生旧字符就替换,不想关的则直接复制
i++;
}
return new String(buf, true); //通过新字符数组buf重构一个新String对象
}
}
return this; //如果old = new ,直接返回自己
}


//替换第一个旧字符
String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

//当不是正规表达式时,与replace效果一样,都是全体换。如果字符串的正则表达式,则规矩表达式全体替换
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

//可以用旧字符串去替换新字符串
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

从replace的算法中,我们可以发现,它不是从头开始遍历替换的,而是首先找到第一个要替换的字符,从要替换的字符开始遍历,发现一个替换一个。但是我暂时没有弄清除这样子的好处是什么,节省时间?应该是吧
四种用法,字符全替换字符,表达式全体换字符,表达式只替换第一个字符,字符串替换字符串

matches()和contains()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* matches() 方法用于检测字符串是否匹配给定的正则表达式。
* regex -- 匹配字符串的正则表达式。
* 如:String Str = new String("www.snailmann.com");
* System.out.println(Str.matches("(.*)snailmann(.*)")); output:true
* System.out.println(Str.matches("www(.*)")); output:true
*/
public boolean matches(String regex) {
return Pattern.matches(regex, this); //实际使用的是Pattern.matches()方法
}

//是否含有CharSequence这个子类元素,通常用于StrngBuffer,StringBuilder
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}

split()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};

// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));

// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}



public String[] split(String regex) {
return split(regex, 0);
}

join()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* join方法是JDK1.8加入的新函数,静态方法
* 这个方法就是跟split有些对立的函数,不过join是静态方法
* delimiter就是分割符,后面就是要追加的可变参数,比如str1,str2,str3
*
* 例子:String.join(",",new String("a"),new String("b"),new String("c"))
* output: "a,b,c"
*/
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter); //就是检测是否为Null,是null,抛异常
Objects.requireNonNull(elements); //不是就返回自己,即nothing happen
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter); //嗯,有兴趣自己看StringJoiner类源码啦
for (CharSequence cs: elements) {
joiner.add(cs); //既用分割符delimiter将所有可变参数的字符串分割,合并成一个字符串
}
return joiner.toString();
}

/**
* 功能是一样的,不过传入的参数不同
* 这里第二个参数一般就是装着CharSequence子类的集合
* 比如String.join(",",lists)
* list可以是一个Collection接口实现类,所含元素的基类必须是CharSequence类型
* 比如String,StringBuilder,StringBuffer等
*/
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}

Java 1.8加入的新功能,有点跟split对立的意思,是个静态方法
有两个重载方法,一个是直接传字符串数组,另个是传集合。传集合的方式是一个好功能,很方遍将集合的字符串元素拼接成一个字符串。(分割符为 “” ,well, It’s great!!)

trim()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 去除字符串首尾部分的空值,如,' ' or " ",非""
* 原理是通过substring去实现的,首尾各一个指针
* 头指针发现空值就++,尾指针发现空值就--
* ' '的Int值为32,其实不仅仅是去空的作用,应该是整数值小于等于32的去除掉
*/
public String trim() {
int len = value.length; //代表尾指针,实际是尾指针+1的大小
int st = 0; //代表头指针
char[] val = value; /* avoid getfield opcode */

//st<len,且字符的整数值小于32则代表有空值,st++
while ((st < len) && (val[st] <= ' ')) {
st++;
}
//len - 1才是真正的尾指针,如果尾部元素的整数值<=32,则代表有空值,len--
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
//截取st到len的字符串(不包括len位置)
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

常见去首尾的空值,实际是去除首尾凡是小于32的字符

toString()函数

1
2
3
4
//emmmmm,这个就不说了吧,就是返回自己
public String toString() {
return this;
}

toCharArray()函数

1
2
3
4
5
6
7
8
9
/**
* 就是将String转换为字符数组并返回
*/
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length]; //定义一个要返回的空数组,长度为字符串长度
System.arraycopy(value, 0, result, 0, value.length); //拷贝
return result; //返回
}

toLowerCase()、toUpperCase()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//en,好长,下次再更新吧,先用着吧
public String toLowerCase(Locale locale) {
if (locale == null) {
throw new NullPointerException();
}

int firstUpper;
final int len = value.length;

/* Now check if there are any characters that need to be changed. */
scan: {
for (firstUpper = 0 ; firstUpper < len; ) {
char c = value[firstUpper];
if ((c >= Character.MIN_HIGH_SURROGATE)
&& (c <= Character.MAX_HIGH_SURROGATE)) {
int supplChar = codePointAt(firstUpper);
if (supplChar != Character.toLowerCase(supplChar)) {
break scan;
}
firstUpper += Character.charCount(supplChar);
} else {
if (c != Character.toLowerCase(c)) {
break scan;
}
firstUpper++;
}
}
return this;
}

char[] result = new char[len];
int resultOffset = 0; /* result may grow, so i+resultOffset
* is the write location in result */

/* Just copy the first few lowerCase characters. */
System.arraycopy(value, 0, result, 0, firstUpper);

String lang = locale.getLanguage();
boolean localeDependent =
(lang == "tr" || lang == "az" || lang == "lt");
char[] lowerCharArray;
int lowerChar;
int srcChar;
int srcCount;
for (int i = firstUpper; i < len; i += srcCount) {
srcChar = (int)value[i];
if ((char)srcChar >= Character.MIN_HIGH_SURROGATE
&& (char)srcChar <= Character.MAX_HIGH_SURROGATE) {
srcChar = codePointAt(i);
srcCount = Character.charCount(srcChar);
} else {
srcCount = 1;
}
if (localeDependent ||
srcChar == '\u03A3' || // GREEK CAPITAL LETTER SIGMA
srcChar == '\u0130') { // LATIN CAPITAL LETTER I WITH DOT ABOVE
lowerChar = ConditionalSpecialCasing.toLowerCaseEx(this, i, locale);
} else {
lowerChar = Character.toLowerCase(srcChar);
}
if ((lowerChar == Character.ERROR)
|| (lowerChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) {
if (lowerChar == Character.ERROR) {
lowerCharArray =
ConditionalSpecialCasing.toLowerCaseCharArray(this, i, locale);
} else if (srcCount == 2) {
resultOffset += Character.toChars(lowerChar, result, i + resultOffset) - srcCount;
continue;
} else {
lowerCharArray = Character.toChars(lowerChar);
}

/* Grow result if needed */
int mapLen = lowerCharArray.length;
if (mapLen > srcCount) {
char[] result2 = new char[result.length + mapLen - srcCount];
System.arraycopy(result, 0, result2, 0, i + resultOffset);
result = result2;
}
for (int x = 0; x < mapLen; ++x) {
result[i + resultOffset + x] = lowerCharArray[x];
}
resultOffset += (mapLen - srcCount);
} else {
result[i + resultOffset] = (char)lowerChar;
}
}
return new String(result, 0, len + resultOffset);
}


public String toLowerCase() {
return toLowerCase(Locale.getDefault());
}


public String toUpperCase(Locale locale) {
if (locale == null) {
throw new NullPointerException();
}

int firstLower;
final int len = value.length;

/* Now check if there are any characters that need to be changed. */
scan: {
for (firstLower = 0 ; firstLower < len; ) {
int c = (int)value[firstLower];
int srcCount;
if ((c >= Character.MIN_HIGH_SURROGATE)
&& (c <= Character.MAX_HIGH_SURROGATE)) {
c = codePointAt(firstLower);
srcCount = Character.charCount(c);
} else {
srcCount = 1;
}
int upperCaseChar = Character.toUpperCaseEx(c);
if ((upperCaseChar == Character.ERROR)
|| (c != upperCaseChar)) {
break scan;
}
firstLower += srcCount;
}
return this;
}

/* result may grow, so i+resultOffset is the write location in result */
int resultOffset = 0;
char[] result = new char[len]; /* may grow */

/* Just copy the first few upperCase characters. */
System.arraycopy(value, 0, result, 0, firstLower);

String lang = locale.getLanguage();
boolean localeDependent =
(lang == "tr" || lang == "az" || lang == "lt");
char[] upperCharArray;
int upperChar;
int srcChar;
int srcCount;
for (int i = firstLower; i < len; i += srcCount) {
srcChar = (int)value[i];
if ((char)srcChar >= Character.MIN_HIGH_SURROGATE &&
(char)srcChar <= Character.MAX_HIGH_SURROGATE) {
srcChar = codePointAt(i);
srcCount = Character.charCount(srcChar);
} else {
srcCount = 1;
}
if (localeDependent) {
upperChar = ConditionalSpecialCasing.toUpperCaseEx(this, i, locale);
} else {
upperChar = Character.toUpperCaseEx(srcChar);
}
if ((upperChar == Character.ERROR)
|| (upperChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) {
if (upperChar == Character.ERROR) {
if (localeDependent) {
upperCharArray =
ConditionalSpecialCasing.toUpperCaseCharArray(this, i, locale);
} else {
upperCharArray = Character.toUpperCaseCharArray(srcChar);
}
} else if (srcCount == 2) {
resultOffset += Character.toChars(upperChar, result, i + resultOffset) - srcCount;
continue;
} else {
upperCharArray = Character.toChars(upperChar);
}

/* Grow result if needed */
int mapLen = upperCharArray.length;
if (mapLen > srcCount) {
char[] result2 = new char[result.length + mapLen - srcCount];
System.arraycopy(result, 0, result2, 0, i + resultOffset);
result = result2;
}
for (int x = 0; x < mapLen; ++x) {
result[i + resultOffset + x] = upperCharArray[x];
}
resultOffset += (mapLen - srcCount);
} else {
result[i + resultOffset] = (char)upperChar;
}
}
return new String(result, 0, len + resultOffset);
}


public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}

format()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
	//JAVA字符串格式化
//新字符串使用本地语言环境,制定字符串格式和参数生成格式化的新字符串。
public static String format(String format, Object... args) {
return new Formatter().format(format, args).toString();
}

//使用指定的语言环境,制定字符串格式和参数生成格式化的字符串。
public static String format(Locale l, String format, Object... args) {
return new Formatter(l).format(format, args).toString();
}
12345678910

24、valueOf类函数
//将Object转换为String

public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}

//将char数组转换为String
public static String valueOf(char data[]) {
return new String(data);
}

//将字符数组的子数组转换为String
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}

//e...重复
public static String copyValueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}

//e...重复
public static String copyValueOf(char data[]) {
return new String(data);
}

//将布尔值转换为String
public static String valueOf(boolean b) {
return b ? "true" : "false";
}

//将单个字符转换为String
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}

//将int转换为String
public static String valueOf(int i) {
return Integer.toString(i);
}

//将long转换为String
public static String valueOf(long l) {
return Long.toString(l);
}

//将float转换为String
public static String valueOf(float f) {
return Float.toString(f);
}

//将double转换为String
public static String valueOf(double d) {
return Double.toString(d);
}

从上看:

copyValueOf和valueOf在Java8看来已经是完全没有区别的函数
所有的value的本质都是新new一个String对象

intern()函数

1
public native String intern();

String类中唯一的一条本地方法,既不是用Java语言实现的方法。
比如str.intern(),作用就是去字符串常量池中寻找str字符串,如果有则返回str在常量池中的引用,如果没有则在常量池中创建str对象

相关问题

String类和CharSequencee接口的关系
CharSequence是一个接口,它只包括length(), charAt(int index), subSequence(int start, int end)这几个API接口。继承了CharSequence代表着这是一个有序的字符集合。

String类为何为不可变类型?

从String的定义,我们可以知道,String类是被final修饰的,是一个不可被继承的类
同时我们从字段属性的private final char value[]可以看出,String的本质char数组也是一个不可变的数组对象,且是私有的,不对外公开。
另外String类也没有提供对value进行修改的方法,所以外部也没有途径对String对象的值进行修改
从其他的方法中,我们也可以看到,如果是对字符串进行修改,其本质是new了一个新的String的对象

注意:
String对象是真的无法修改吗?
答案是否定的,还是有途径修改的,那就是通过反射。
因为final的是字符数组,只代表这个value变量的引用不能改变,不代表value指向的对象不可以改变,String只是没有提供修改的途径,即使有也会是不对外公开。但数组中的元素是可以修改的,虽然没有途径,但还是反射就是一个逆天的存在,我们可以通过反射的途径去修改字符数组value的元素的值。
所以String的不可变性仅仅是正常情况下的不可变,但绝非完全的不可变。

String类length与codePointCount的区别
对于普通字符串,这两种方法得到的值是一样的,但对于UniCode编码来说,还是有一点区别。
区别:
length()方法返回的是使用的是UTF-16编码的字符代码单元数量,不一定是实际上我们认为的字符个数。codePointCount()方法返回的是代码点个数,是实际上的字符个数。
例如:
String str = “/uD835/uDD6B”,那么机器会识别它是2个代码单元代理的1个代码点”Z“,故而,length的结果是代码单元数量2,而codePointCount()的结果是代码点数量1.

为什么String已经有一个compareTo方法了,还需要一个静态内部类再实现compare?
这里有一个疑惑,在String中已经有了一个compareTo的方法,为什么还要有一个CaseInsensitiveComparator的内部静态类呢?网上的回答大多数都是说是为了代码复用,我觉得这是有道理的。

首先是一个代码复用的问题,因为String类的compareToIgnoreCase方法实际调用的也是静态内部的compare方法
其次这里采用了饿汉单例模式

个人想法:
我个人觉得,为什么要有个静态内部类呢,首先我们要区别comparable和comparator接口的区别。实现了comparable接口的类,我们就能够本类的内部重写compareTo方法实现大小比较,从而给Collections.sort或Arrays.sort进行排序。而如果一个类没有实现comparable接口也想该类可以排序怎么办,那么就可以创造一个外部比较器去实现comparator接口,重写compare方法。以达到可以给Collections和Arrays使用。
具体区别参考如下链接:
Comparator和Comparable接口的区别
因为String继承了comparable接口,所以可以重写compareTo(String str)方法,注意参数是String类型的。里面实现的方式不忽略大小写的比较方式。如果我们还需要一个可以忽视大小写的比较方法怎么办?我们可能立马想到的就是重载一个compareTo方法,多对个参数?问题是不行的,工具类只认compareTo(String str),那么我们此时要怎么办呢?所以只能求救于Comparetor接口。要用Comparetor接口必须创造一个比较器去实现Comparator接口,并重写compare方法。所以就有了String内部就有了实现Comparator接口的静态内部类,这个静态内部类就是一个比较器。(当然这个静态内部类也可以不放进String类里。可以单独作为一个类,可是没有必要,因为这个类也就在String内部使用,所以作为静态内部类即可。)

为什么在忽略大小写比较的时候,通常都会大小写都会比较一次?
也就是说,通常会同位置的字符会全部转为大写比较一次,又转为小写比较一次?这是为什么,不是转换为大写或小写比较一次既可吗?
同时比较了UpperCase和LowerCase,是为了兼容Georgian字符。
https://www.cnblogs.com/yanyichao/p/4493039.html

String类的CopyValueOf 和ValueOf有什么不同吗?
目前Java8的源码来看,没有任何区别。
这里引用百度知道@作者:渊之蓝的一条答案:
我觉得你最大的疑问应该在char数组上吧?比如
char[] test = new char[]{‘a’,‘b’,‘c’};
String a = String.valueOf(test);
String b = String.copyValueOf(test);
有什么区别?答案:没有任何区别!
首先你得知道,String的底层是由char[]实现的:通过一个char[]类型的value属性!早期的String构造器的实现呢,不会拷贝数组的,直接将参数的char[]数组作为String的value属性。然后
test[0] = ‘A’;
将导致字符串的变化。为了避免这个问题,提供了copyValueOf方法,每次都拷贝成新的字符数组来构造新的String对象。但是现在的String对象,在构造器中就通过拷贝新数组实现了,所以这两个方面在本质上已经没区别了。

作者:SnailMann
来源:CSDN
原文:https://blog.csdn.net/SnailMann/article/details/80882719
版权声明:本文为博主原创文章,转载请附上博文链接!

idea的常用操作

发表于 2019-03-18 | 分类于 工具的使用

必备的快捷键

查找相关

1
2
3
4
5
6
Ctrl + F    在当前文件进行文本查找
Ctrl + R 在当前文件进行文本替换
Ctrl + N 根据输入的 名/类名 查找类文件
Ctrl + Shift + F 根据输入内容查找整个项目 或 指定目录内文件
Ctrl + Shift + R 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件
连按两次Shift 弹出 Search Everywhere 弹出层

显示相关信息

1
2
3
4
5
6
Ctrl + P    方法参数提示显示
Ctrl + U 前往当前光标所在的方法的父类的方法 / 接口定义
Ctrl + Shift + B 跳转到类型声明处
Ctrl + 光标定位按 Ctrl 不要松开,会显示光标所在的类信息摘要
Ctrl + Shift + 左键单击 把光标放在某个类变量上,按此快捷键可以直接定位到该类中
Alt + Enter IntelliJ IDEA 根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同

常用的快捷键

1
2
3
4
5
6
7
8
Ctrl + /    注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
Ctrl + Z 撤销
Ctrl + Shift + Z 取消撤销 (必备)
Alt + Insert 代码自动生成,如生成对象的 set / get 方法,构造函数,toString()
Ctrl + Y 删除光标所在行 或 删除选中的行
Shift + Tab 取消缩进
Ctrl + C 复制
Ctrl + V 粘贴

所有的快捷键描述可以看这篇博文

设置字体和样式

设置界面字体和样式

点击File->setting->Appearance & Behavior > Appearance

选择自己喜欢的大小和样式。最后单击apply和ok保存设置

设置代码的字体和样式

点击File->setting->Editor->font

选择自己喜欢的大小和样式。最后单击apply和ok保存设置

控制台的字体和样式

点击File->setting->Editor->Color Scheme->Console Font


选择自己喜欢的大小和样式。最后单击apply和ok保存设置

设置主题

点击File->setting->Appearance & Behavior > Appearance

有三种主题Darcula、IntelliJ和High contrast,选择你喜欢的,最后单击apply和ok保存设置

idea的缩写

常用缩写

1
2
3
4
5
6
7
8
psvm = public static void main(String[] args) {}
sout = System.out.println();
soutm = System.out.println("当前类名.当前方法");
soutp = System.out.println("");
soutv = System.out.println("变量名 = " + 变量);
psf = public static final
psfi = public static final int
psfs = public static final String

自定义缩写

点击File->setting->Editor->Live Template

创建一个Template Group这里取名为test

之后在test中创建一个Live Template

标注1是指缩写;标注2是指缩写表示的具体代码;标注3是指缩写的适用范围,点击选择范围

最后单击apply和ok保存设置.之后你就可以使用这个缩写了。

修改IDEA自带的缩写

点击File->setting->Editor->Live Template->other,按自己的喜好修改

自定义类模板

点击File->setting->Editor->File and Code Templates–>Includes,加入类模板注释

1
2
3
4
5
6
/**
* @Classname ${NAME}
* @Description TODO
* @Date ${DATE} ${TIME}
* @Created by ${USER}
*/

插件

Background Image Plus

idea背景修改插件,可以设置自己喜欢的图片作为code背景.安装教程

Alibaba Java Coding Guidelines

阿里巴巴代码规范检查插件,规范可以参考《阿里巴巴Java开发手册》。安装教程

Gsonformat

可根据json数据快速生成java实体类。安装教程

Translation

翻译插件.安装教程

更多插件介绍看这里

如果需要了解更多的idea教程看JaJian的博客和史上最简单的 IntelliJ IDEA 教程

Java源码分析---Integer

发表于 2019-03-17 | 分类于 java源码分析

公有常量介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
*表示int的最小值 - 2^31
*/
@Native public static final int MIN_VALUE = 0x80000000;

/**
*表示int的最大值 2^31 - 1
*/
@Native public static final int MAX_VALUE = 0x7fffffff;

/**
* 表示基本类型int的实例
*/
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

常用方法的源码分析

toString

1
2
3
public static String toString(int i, int radix)//radix代表进制,用来把i转化为指定进制字符串

public static String toString(int i)//把i转化为十进制字符串

toString(int i, int radix)的源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

/**
* 所有可能用来把数字表示为字符串的字符
*/
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)//Character.MIN_RADIX,Character.MAX_RADIX分别为2和36
radix = 10;

/* 如果转化为十进制,则用toString,这个方法更快*/
if (radix == 10) {
return toString(i);
}

char buf[] = new char[33];//由于表示Integer的最小值的二进制需要33位
boolean negative = (i < 0);//判断i是否为负数
int charPos = 32;//记录位置

if (!negative) {
i = -i;
}

/*这段代码通过基数取余,来获取每一位的值,如果看不懂,直接带入十进制来看*/
/*首先:设 i = 123 radix = 10*/
/*第一步:i = -123(上面i=-i) < -10成立,循环开始*/
/*第二步:buf[32] = digits[3] = 3,charPos = 31 digits在上面常量里有*/
/*第三步:i = 123/10 = 12*/
/*重复这一过程*/
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
//见过首位(例如上面123的1)放入数组
buf[charPos] = digits[-i];

if (negative) {//判断是否需要加上 -
buf[--charPos] = '-';
}
//返回结果
return new String(buf, charPos, (33 - charPos));
}

toString(int i)的源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 public static String toString(int i) {
if (i == Integer.MIN_VALUE)//如果等于最小值的话,直接返回
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);//获取i的长度,看下面
char[] buf = new char[size];
getChars(i, size, buf);//将数字i的各位存入数组中
return new String(buf, true);
}

final static char [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
} ;

final static char [] DigitOnes = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
} ;

static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;

if (i < 0) {
sign = '-';
i = -i;
}

// 每次循环生成两位数
while (i >= 65536) {
q = i / 100;
r = i - ((q << 6) + (q << 5) + (q << 2));//表示 r = i - (q * 100);
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}

// 当 i < 65536时执行这个
for (;;) {
q = (i * 52429) >>> (16+3);//结果近似于 q = 0.1*i
r = i - ((q << 3) + (q << 1)); // 代表r = i-(q*10)
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}

final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };



static int stringSize(int x) {
for (int i=0; ; i++)//根据与sizeTable中数的比较,得出x的长度
if (x <= sizeTable[i])
return i+1;
}

parseInt

parseInt的源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public static int parseInt(String s, int radix)//radix表示要转为的进制数
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/

if (s == null) {
throw new NumberFormatException("null");
}

if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}

if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}

int result = 0;//记录最后的结果
boolean negative = false;//判断是否位负数
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;

if (len > 0) {
char firstChar = s.charAt(0);//获取首位字符,判断是否为 - 或 +
if (firstChar < '0') {
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);

if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
//如果看不懂,可以带入我们熟悉的十进制了解算法过程
while (i < len) {
//获取字符,并转为int
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}

public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}

valueOf

valueOf的源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Integer valueOf(String s, int radix) throws NumberFormatException {//调用valueOf(int i)方法
return Integer.valueOf(parseInt(s,radix));
}

public static Integer valueOf(String s) throws NumberFormatException {//调用valueOf(String s, int radix),且radix默认为10
return Integer.valueOf(parseInt(s, 10));
}

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)//IntegerCache.low = -128 IntegerCache.high = 127,如果i在这两个数之间,就调用IntegerCache进行缓存
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);//否则之间创建一个Integer
}

构造器

1
2
3
4
5
6
7
8
9
private final int value;

public Integer(int value) {
this.value = value;
}

public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}

从上面源码可以看出给Integer赋的值是不可以改变的,由于private final int value;是常量。但是在编程中
我们可以通过Integer i = 1; i = 3;直接赋值,这是怎么回事

我们将上面的代码进行反编译,反编译之后的代码如下:

1
2
Integer i = new Integer(1);
i = Integer.valueOf(3);

IntegerCache

转载这里

本文将介绍 Java 中 Integer 缓存的相关知识。这是 Java 5 中引入的一个有助于节省内存、提高性能的特性。
首先看一个使用 Integer 的示例代码,展示了 Integer 的缓存行为。接着我们将学习这种实现的原因和目的。
你可以先猜猜下面 Java 程序的输出结果。很明显,这里有一些小陷阱,这也是我们写这篇文章的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**

* 测试Integer的缓存 IntegerCache.cache

*/

private static void testIntegerCache() {

System.out.println("---int---");

int a = 127, b = 127;

System.out.println(a == b); //true

a = 128;

b = 128;

System.out.println(a == b); //true



System.out.println("---Integer---");

Integer aa = 127, bb = 127;

System.out.println(aa == bb); //true

aa = 128;

bb = 128;

System.out.println(aa == bb); //false

System.out.println(aa.equals(bb)); //true

}

执行结果就如同上面的那样,我就不贴图来展示啦,我把原文的例子给换成自己的了。

在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。
上面的规则适用于整数区间 -128 到 +127。
这种 Integer 缓存策略仅在自动装箱(autoboxing)的时候有用,使用构造器创建的 Integer 对象不能被缓存。
Java 编译器把原始类型自动转换为封装类的过程称为自动装箱(autoboxing),这相当于调用 valueOf 方法

1
2
Integer a = 10; //this is autoboxing
Integer b = Integer.valueOf(10); //under the hood

现在我们知道了 JDK 源码中对应实现的部分在哪里了。我们来看看 valueOf 的源码。下面是 JDK 1.8.0 build 25 中的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**

* Returns an {@code Integer} instance representing the specified

* {@code int} value. If a new {@code Integer} instance is not

* required, this method should generally be used in preference to

* the constructor {@link #Integer(int)}, as this method is likely

* to yield significantly better space and time performance by

* caching frequently requested values.

*

* This method will always cache values in the range -128 to 127,

* inclusive, and may cache other values outside of this range.

*

* @param i an {@code int} value.

* @return an {@code Integer} instance representing {@code i}.

* @since 1.5

*/

public static Integer valueOf(int i) {

if (i >= IntegerCache.low && i <= IntegerCache.high)

return IntegerCache.cache[i + (-IntegerCache.low)];

return new Integer(i);

}

在创建新的 Integer 对象之前会先在 IntegerCache.cache (是个Integer类型的数组)中查找。有一个专门的 Java 类来负责 Integer 的缓存。

IntegerCache 类

IntegerCache 是 Integer 类中一个私有的静态类。我们来看看这个类,有比较详细的文档,可以提供我们很多信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/

private static class IntegerCache {

static final int low = -128;

static final int high;

static final Integer cache[];

static {

// high value may be configured by property

int h = 127;

String integerCacheHighPropValue =

sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

if (integerCacheHighPropValue != null) {

try {

int i = parseInt(integerCacheHighPropValue);

i = Math.max(i, 127);

// Maximum array size is Integer.MAX_VALUE

h = Math.min(i, Integer.MAX_VALUE - (-low) -1);

} catch( NumberFormatException nfe) {

// If the property cannot be parsed into an int, ignore it.

}

}

high = h;



cache = new Integer[(high - low) + 1];

int j = low;

for(int k = 0; k < cache.length; k++)

cache[k] = new Integer(j++);



// range [-128, 127] must be interned (JLS7 5.1.7)

assert IntegerCache.high >= 127;

}



private IntegerCache() {}

}

Javadoc 详细的说明这个类是用来实现缓存支持,并支持 -128 到 127 之间的自动装箱过程。最大值 127 可以通过 JVM 的启动参数 -XX:AutoBoxCacheMax=size 修改。 缓存通过一个 for 循环实现。从小到大的创建尽可能多的整数并存储在一个名为 cache 的整数数组中。这个缓存会在 Integer 类第一次被使用的时候被初始化出来。以后,就可以使用缓存中包含的实例对象,而不是创建一个新的实例(在自动装箱的情况下)。

实际上在 Java 5 中引入这个特性的时候,范围是固定的 -128 至 +127。后来在 Java 6 中,最大值映射到 java.lang.Integer.IntegerCache.high,可以使用 JVM 的启动参数设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。是什么原因选择这个 -128 到 127 这个范围呢?因为这个范围的整数值是使用最广泛的。 在程序中第一次使用 Integer 的时候也需要一定的额外时间来初始化这个缓存。

Java 语言规范中的缓存行为

在 Boxing Conversion 部分的Java语言规范(JLS)规定如下:
如果一个变量 p 的值属于:-128至127之间的整数(§3.10.1这个估计是版本号吧),true 和 false的布尔值 (§3.10.3),’u0000′ 至 ‘u007f’ 之间的字符(§3.10.4)中时,将 p 包装成 a 和 b 两个对象时,可以直接使用 a == b 判断 a 和 b 的值是否相等。

其他缓存的对象
这种缓存行为不仅适用于Integer对象。我们针对所有整数类型的类都有类似的缓存机制。
有 ByteCache 用于缓存 Byte 对象
有 ShortCache 用于缓存 Short 对象
有 LongCache 用于缓存 Long 对象
有 CharacterCache 用于缓存 Character 对象
Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。

菜鸟话Java---类和对象

发表于 2019-03-13 | 分类于 java

类和对象的概念

类(class)是构造对象的模板或蓝图。可以把类比作书,而对象比作一本本具体的书,比如课本,小说等。类是抽象的概念,对象是具体的实体。

对象的具体特征

  • 对象的行为 —— 可以对对象施加哪些操作,或可以对对象施加哪些方法
  • 对象的状态 —— 当施加哪些方法时,对象应该任何响应
  • 对象标识 —— 如何辨别具有相同行为而状态不同的对象

封装

封装是将数据和行为组合在一个类中,并对对象的使用者隐藏了数据的实现方式。可以想象你在自动取款机中取钱的情形,你并不知道自动取款机是如何取出你所需要的钱的,你也不需要知道。当自动取款机的取款的原理发生改变时(可能原来的有安全隐患),但你所要做的仍然是输入取款金额,然后取钱。这就是封装的好处。

一个Java的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class People {

public String name;

public int age;

public String adress;

public void sleep(){
System.out.println("睡觉");
}

public void work(){
System.out.println("工作");
}

}

如上示例是一个表示人的Java类,下面我们来分析这个类:

首先class 类名表示一个类,这个上一篇文章介绍过,不用多说。public是公有的意思,即所以类都看。在这个类中我们定义了两个属性name和age,这个也叫做成员变量。
我们又定义了人的行为sleep和work,这个在Java中叫做方法或函数.

现在我们创建了人这个模板,那么怎么创建具体的人的对象,这就要用到new,具体代码如下:

1
2
3
4
5
6
7
public static void main(String[] args) {
People people = new People();//获取对象的引用
people.name = "小明";//对象使用 . 运算符来调用对象的方法或引用成员变量
people.age = 18;
people.sleep();
people.work();
}

Private

下面我们来扩展这个Java类,我们给它再添加一个是否有女朋友的属性,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  public class People {

String name;

int age;

boolean isHaveGirlFriend;

public void sleep(){
System.out.println("睡觉");
}

public void work(){
System.out.println("工作");
}

}

试想一下,如果你问陌生人是否有女朋友,帅哥还好,要是一个肥仔(比如我)就不想理你;但是你可以直接调用
people.isHaveGirlFriend来看我是否有女朋友,这就有点侵犯隐私了。可是我们怎么限制别人对对象的调用(即把成员变量声明为私有域)
,这就要使用private关键字,private关键字修饰的属性或方法只能再本类中可见,一般所有的成员变量要用private修饰.(之后会详细介绍Java 访问修饰符)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class People {

String name;

int age;

private boolean isHaveGirlFriend;

public void sleep(){
System.out.println("睡觉");
}

public void work(){
System.out.println("工作");
}

public boolean isHaveGirlFriend() {//通过调用方法来访问属性,可以控制属性的访问,方法名一般取set/get+成员变量名,boolean变量用is+变量名,注意首字母大写
return isHaveGirlFriend;
}

public void setHaveGirlFriend(boolean haveGirlFriend) {
isHaveGirlFriend = haveGirlFriend;
}
}

同理方法用private修饰是私有方法

静态域和静态方法

静态域

有时候我们会想要使用一个概念,而不是一个具体的对象来做一些事情。比如要统计人类的总数,那就必须针对人类这个抽象概念,并且
要有属于这个抽象的变量(如人的总数这个变量),而不是每个对象的成员变量(比如说每个人的年龄,你不能说人类的年龄)。要实现这个
抽象的变量(即静态域),只需添加static关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class People {

String name;

int age;

private static int count = 0;//声明一个静态域,表示人类的总数,尽量使用private

private boolean isHaveGirlFriend;

public void sleep(){
System.out.println("睡觉");
}

public void work(){
System.out.println("工作");
}

public boolean isHaveGirlFriend() {
return isHaveGirlFriend;
}

public static void add(){
count++;
}

public void setHaveGirlFriend(boolean haveGirlFriend) {
isHaveGirlFriend = haveGirlFriend;
}
}

每个对象对于类的成员变量都有自己的拷贝,比如1000个人类对象,就有1000个成员变量name,但是只有一个静态域人类的总数count

注意:静态域只能通过类名.静态域变量名来直接访问,所有也可以称为类域

静态常量

静态常量即可以直接通过类名.常量名直接访问的量,格式如下:

1
public static final int COUNT = 1;

静态方法

静态方法是只能通过类名.静态方法名调用的方法;静态方法的定义和静态域一样,只需要加上一个static即可。

注意:静态方法只能访问类中的静态域,而不是类的成员变量

方法

方法的定义访问修饰符 返回值 方法名(参数类型 参数名 ...){ }

1
2
3
4
5
public void sleep(){System.out.println("睡觉");}//修饰符为public   void表示无返回值  sleep是方法名 System.out.println("睡觉");是方法执行的功能

public String getName(int id){//这是个通过指定参数返回用户姓名的方法 String表示返回参数类型 int id 是方法的参数
return names[id]; // return 关键字表示返回指定参数 names[id]是返回的参数
}

注意:return会结束所在的方法,例如

1
2
3
4
5
6
public void sleep()
{
if (true)
return;//直接退出方法
System.out.println("睡觉");//永远无法访问
}

局部变量

  • 局部变量声明在方法、构造方法或者语句块中;
  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
  • 访问修饰符不能用于局部变量;
  • 局部变量只在声明它的方法、构造方法或者语句块中可见;
  • 局部变量是在栈上分配的。
  • 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

成员变量(实例变量)

  • 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
  • 当一个对象被实例化之后,每个实例变量的值就跟着确定;
  • 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
  • 访问修饰符可以修饰实例变量;
  • 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
  • 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;
  • 实例变量可以直接通过变量名访问

重载

如果多个方法有相同的名字,不同的参数,那这多个方法重载。例如:

1
2
3
4
5
6
7
8
9
10
11
public boolean equal(int a,int b){
return a == b;
}

public boolean equal(String a,String b){
return b.equals(a);
}
//这个方法与上面的方法不能重载,由于返回类型不能作为重载判断的方式
public int equal(String a,String b){
return a.compareTo(b);
}

注意:Java允许重载任何方法。因此要完整地描述一个方法,需要指出方法名以及参数类型,这叫做方法
的签名。返回类型不是方法签名的一部分。

构造器

1
2
3
public 类名(参数...){

}

构造器的语法:

  • 构造器与类名相同
  • 每个类可以有一个以上的构造器,如在类中没有显示的声明一个构造器,系统就会提供一个无参数的构造器
  • 构造器可以有0个,1个或多个参数
  • 构造器没有返回值
  • 构造器总是伴随着new操作一起调用
  • 构造器中不能定义与成员变量同名的局部变量

注意:仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。如果自己给出了一个构造器,要想使用系统默认的构造器就必须自己
提供一个默认构造器

1
2
3
4
5
6
7
8

public People(){//默认构造器

}

public People(String name){

}

this

this关键字表示这个对象的引用

作用1:引用方法的隐式参数

1
2
3
public People(String name){
this.name = name;//this.name表示成员变量name,name表示构造方法中的name
}

作用2:调用构造器

1
2
3
4
5
6
7
 public People(){//默认构造器
this("无名氏");//调用下面的构造器
}

public People(String name){
this.name = name;
}

对象变量(或引用变量)

我们已经知道People people1 = new People();是获取一个对象的引用;实际上一个对象变量有没有包含一个对象,而仅仅是
引用一个对象。在Java中任何对象变量的值都是对储存在另一个地方的一个对象的引用。new操作符的返回值也是一个引用,如图:

注意:People people;没有引用对象

方法参数

按值调用 : 表示方法接受的是调用者提供的值,Java是按值调用
按引用调用 : 表示方法接受的是调用者提供的变量地址

Java中方法参数的使用情况:

  • 一个方法不能修改一个基本类型的参数
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能让对象参数引用一个新的对象

包

在定义一个类时,要给类取名字,类名一般要根据类的作用来取,而不是毫无意义的字符;不过随着定义的类
越来越多,类名很有可能重复。为了解决这个问题,我们就采用包(package)

类的导入

我们使用别的包中的公有类时,我们有两种方式来访问:第一种是通过每个类前面直接添加完整的包名。如:

1
java.time.LocalDate localDate = java.time.LocalDate.now();

第二种是导入类,导入类的关键字import,语法如下

1
2
3
import 包名.类名;//导入单个类

import 包名.*;//导入这个包中所有的文件

例如:

1
2
3
4
import java.time.LocalDate;
...
//之后就可以直接使用LocalDate类了
LocalDate localDate = LocalDate.now();

注意:如果一个类中出现重名的类,编译器无法确定使用哪个;那就必须在每一个(重名的)类名
前面加上完整的包名

静态导入

import不仅可以导入类,还可以导入静态方法和静态域

例如

1
import static java.lang.System.*;//之后就可以直接使用System的静态方法和静态变量

将类放入包中

要想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。例如:

1
2
3
4
5
package com.demo.core;

public class People{
...
}

包的作用域

如果类或类中变量,方法没有被private或public修饰,那么这个部分(类,方法和变量)可以被同一包中所有方法访问

类设计技巧

  • 一定要保证数据私有
  • 一定要对数据初始化
  • 不要在类中使用过多的基本类型
  • 不是所有的域都需要独立的域访问器和域修改器
  • 将职责过多的类进行分解
  • 类名和方法名要能够体现它们的职责
  • 优先使用不可变的类

菜鸟话Java--java基本语法

发表于 2019-03-10 | 分类于 java

一个简单的Java程序

1
2
3
4
5
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

上面是一个简单的Java程序,作用是在控制台输出Hello World!。下面来分析这段代码

public

public是访问修饰符,这些修饰符用于控制程序的其他部分对这段代码的访问级别。访问
修饰符包括public private protect和默认修饰符.关于访问修饰符,后面会具体介绍。

class Main

class是关键字,class 类名用来定义一个类,Java程序的所有内容都必须放在类中。

Java类名的规则:

  1. 类名不能以数字开头,必须是字母
  2. 不能使用Java保留字(后面会介绍Java保留字)

Java类名的规范:(注意,规则是强制要求的,下面规范不是。不过遵守规范有利于代码结构清晰,建议遵守规范)

  1. 类名是以大写首字母开头的名词,如First
  2. 如果类名由多个单词组成,则每个单词首字母都应该大写(即驼峰命名法),如FirstSimple

public static void main(Strinf[] args){…}

这个是在类Main中定义了一个方法。static是指静态,void表示无返回值,main是方法名(后面的文章会介绍)。注意:main
方法是一个特殊的方法,每一个Java应用程序都必须有一个main方法,没有main方法程序将不能执行,而且main方法的修饰符必须是public。

java的基本数据类型

java是一种强类型语言,这意味着必须给每一个变量声明一种类型。在Java中一共有八种基本类型,它们分别是
整形(byte,short,int,long),浮点型(float,double),字符型(char),表示真值的(boolean)

整形

整形表示没有小数部分的值,它允许是负数.java有四种整形,具体区别如下:

类型 储存需求 取值范围
int 4字节 -2,147,483,648(-2^31)~ 2,147,483,647(2^31 - 1)(正好超过20亿)
short 2字节 -32768(-2^15)~ 32767(2^15 - 1)
byte 1字节 -128(-2^7)~ 127(2^7-1)
long 8字节 -9,223,372,036,854,775,808(-2^63)~ 9,223,372,036,854,775,807(2^63 -1)

注意:long类型后面通常有L或l(l容易和1混淆,所以推荐使用大写的L),如2000000000000000L

二进制,八进制,十六进制

二进制 : 加上前缀0b或0B表示二进制,如0b1001表示9

八进制 : 加上前缀0表示八进制,如010表示8,不过八进制表示法容易混淆,所以不推荐使用

十六进制 : 加上前缀0x或0X表示十六进制.

从Java 7 开始,还可以为数字字面量加下划线,如用1_000_000表示一百万.这些下划线只为了让人更
易读,Java编译器会除去这些下划线。

浮点型

浮点型用于表示有小数部分数值的类型,在Java中有两种浮点类型,具体的区别如下:

类型 储存需求 取值范围
float(单精度浮点值) 4节字 float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
double(双精度浮点值) 8字节 double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数

float类型的数值有一个后缀f或F,如果没有后缀的浮点值默认为double(也可以往浮点值后面加d或D,表示double类型)

特殊的浮点数值 :

  • 正无穷大
  • 负无穷大
  • NaN 表示0/0或负数的平方根,不是一个数字

注意:不能用浮点型做精确运算;更不能使用while( i == 浮点数)作为循环条件,由于二进制本身的特性。

char

char类型的值用’’表示,如char a = 'a'.具体的关于char的说明请看后面的文章

char类型特殊字符的转义序列

转义序列 名称 Unicode值
\b 退格 \u0008
\t 制表 \u0009
\n 换行 \u000a
\r 回车 \u000d
\” 双引号 \u0022
\’ 单引号 \u0027
\ 反斜杠 \u005c
– ™ \u2122
– π \u03c0

boolean类型

boolean(布尔)类型有两个值true和false.用来判断逻辑条件

String

String表示字符串类型如”abcdefg”,与上面的基本类型不同,String是引用类型(具体的后面会介绍)

数组

声明数组变量

首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:

1
2
3
dataType[] arrayRefVar;   // 推荐

dataType arrayRefVar[]; // 效果相同,但不是首选方法

注意: 建议使用 dataType[] arrayRefVar 的声明风格声明数组变量。 dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,
在Java中采用是为了让 C/C++ 程序员能够快速理解java语言。

1
2
3
double[] myList;         // 首选的方法

double myList[]; // 效果相同,但不是首选方法

创建数组

Java语言使用new操作符(后面会介绍)来创建数组,语法:arrayRefVar = new dataType[arraySize];

上面的语法语句做了两件事:

  • 使用 dataType[arraySize] 创建了一个数组。
  • 把新创建的数组的引用赋值给变量 arrayRefVar。
1
2
3
4
//数组变量的声明,和创建数组可以用一条语句完成,如下所示:
dataType[] arrayRefVar = new dataType[arraySize];
//另外,你还可以使用如下的方式创建数组。
dataType[] arrayRefVar = {value0, value1, ..., valuek};

数组的元素是通过索引访问的。数组索引从 0 开始,所以索引值从 0 到 arrayRefVar.length-1。

数组的拷贝

1
2
3
4
5
6
//错误示例,这种方式两个数组变量将引用同一个数组
int[] a1 = new int[3];
int[] a2 = a1

//正确示例
int[] a3 = Arrays.copyOf(a1,a1.length);

注释

在Java中有三种标记注释的方式

1
2
3
4
5
6
7
8
9
10
//单行注释

/*
*多行注释
*/

/**
* Java doc,这里的注释会生成文档
*
*/

变量

声明变量

在Java中声明变量的语法是类型 变量名,例如

1
2
3
4
int a;
boolean A;//Java是大小写敏感的
float c;
double m,n;//不推荐

注意:每个变量声明都必须以;结尾

变量初始化

1
2
3
int a = 1;//直接初始化
int b;
b = 3;//对已经声明过的变量进行初始化

注意:声明一个变量之后,必须进行初始化,不能使用未初始化的值,否则会报错。(成员变量声明后有默认值,已经初始化了,所以可以直接使用)

常量

在Java中,利用关键字final指示常量(常量是指不会变的量),例如:

1
final int A = 10;//必须直接赋值,赋值后就不能再更改,习惯上常量名全大写

运算符

算术运算符

操作符 描述 例子
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取余 a % b
++ 自增 有a++和++a两种,具体区别见下文
– 自减 有a--和--a两种,具体区别见下文

a++和++a的区别:

++a:先进行自增运算,再进行表达式运算,而a++先进行表达式运算,再进行自增运算.(a--和--a类似)

1
2
3
4
int a = 1;
int b = 1;
System.out.println(a++);//先执行println方法,再执行a++,输出结果为 1
System.out.println(++b);//先执行b++,再执行println方法,输出结果为 2

关系运算符

运算符 描述
== 检查如果两个操作数的值是否相等,如果相等则条件为真。
!= 检查如果两个操作数的值是否相等,如果值不相等则条件为真。
> 检查左操作数的值是否大于右操作数的值,如果是那么条件为真。
< 检查左操作数的值是否小于右操作数的值,如果是那么条件为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。

逻辑运算符

操作符 描述
&& 当且仅当两个操作数都为真,条件才为真。
` ` 如果任何两个操作数任何一个为真,条件为真。
! 用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。

位移运算符

操作符|描述|
|—–|—-|
|&|与运算|
|||或运算|
|〜|按位取反运算符|
|<<|按位左移运算符|
|>>|按位右移运算符|
|>>>|按位右移补零操作符|

赋值运算符

操作符 描述 例子
= 赋值运算符 —
+= 加和赋值操作符 C += A等价于C = C + A
-= 减和赋值操作符 C -= A等价于C = C-A
* = 乘和赋值操作符 C = A等价于C = C A
/= 除和赋值操作符 C / = A等价于C = C / A

以上只列举了比较常用的运算符,具体的运算符可以看菜鸟教程

java循环结构

while循环

while循环的基本形式:

1
2
3
while(布尔表达式){
//循环语句,只要布尔表达式一直为true,循环就会一直执行下去
}

do…while循环

基本形式:

1
2
3
do{
//循环语句,只要布尔表达式一直为true,循环就会一直执行下去
}while(布尔表达式);

do...while循环和while循环的区别?

do...while循环中的语句至少会执行一次,而while循环里面的语句至少执行0次

for循环

例如:

1
2
3
for (int i = 0; i < 10; i++) {
System.out.println(i);
}

首先i = 0与10比较,其结果为true,执行System.out.println(i);语句;之后执行i++,此时i = 1,再与10进行比较,直到i < 10的结果为false为止.

增强 for 循环

示例:

1
2
3
4
int a[] = {1,2,3,4,5};
for (int c : a) {
System.out.println(c);
}

break和continue

break

break主要用在循环语句或者 switch 语句中,用来跳出整个语句块。

break的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
break;  //是跳出里层循环(针对几层循环而言))

//示例如下,下面这段代码将一直循环运行
while (true){
for (int i = 0; i < 20 ; i++) {
if (i == 3)
break ;
System.out.println(i);
}
}

break name; //跳出指定名字的循环

//示例如下,输出结果为 0 1 2

name:while (true){
for (int i = 0; i < 20 ; i++) {
if (i == 3)
break name;
System.out.println(i);
}
}

continue

continue 适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。

  • 在 for 循环中,continue 语句使程序立即跳转到更新语句。
  • 在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。
  • continue也可以指定循环的名字,直接跳到该循环

条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//if语句
if(布尔表达式)
{
//如果布尔表达式为true将执行的语句
}

//if...else语句
if(布尔表达式){
//如果布尔表达式的值为true
}else{
//如果布尔表达式的值为false
}

//if...else if...else语句
if(布尔表达式 1){
//如果布尔表达式 1的值为true执行代码
}else if(布尔表达式 2){
//如果布尔表达式 2的值为true执行代码
}else if(布尔表达式 3){
//如果布尔表达式 3的值为true执行代码
}else {
//如果以上布尔表达式都不为true执行代码
}

switch

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch(expression){
case value1 :
//语句
break; //可选
case value2 :
//语句
break; //可选
case value3 :
//语句
break; //可选
//你可以有任意数量的case语句
default : //可选
//语句
}

注意:

  • expression变量类型可以是: byte、short、int 或者 char。从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。
  • 如果expression与任意case中的值匹配,就相当于打开了开关,之后的值不会再比较而是直接运行语句,直到碰到过break.

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
char grade = 'C';

switch(grade)
{
case 'A' :
System.out.println("优秀");
break;
case 'B' :
case 'C' :
System.out.println("良好");
break;
case 'D' :
System.out.println("及格");
break;
case 'F' :
System.out.println("你需要再努力努力");
break;
default :
System.out.println("未知等级");
}

菜鸟话Java——Java概述

发表于 2019-03-08 | 分类于 java

java是什么

Java是由Sun Microsystems(后来被Oracle收购)公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称,是一个高级程序设计语言。

Java的主要特性

  • 简单性
  • 面向对象
  • 分布式
  • 健壮性
  • 安全性
  • 体系中立性
  • 可移植性
  • 解释型
  • 高性能
  • 多线程
  • 动态性

具体的可以百度或在菜鸟教程上查看

Java的主要的应用场景

  • web后端
  • Android app开发

Java各种术语介绍

术语名 缩写 解释
Java Development kit JDK 编写Java程序的程序员使用的软件
Java Runtime Environment JRE 运行Java程序所必须的环境
Server JRE — 运行于服务器上的JRE
Standard Edition SE java标准版,主要学习这个
Enterprise Edition EE 用于服务器应用,现在不常用
Micro Edition ME 用于手机和其他小型设备的Java平台,由于洛基亚手机破产后,现在不常用
Java FX — 用于图形化界面的一个替代工具包
OpenJDK — java se一个免费开源版本

java有关的问题

Java和JavaScript有什么关系?

javaScript是一种在网页上使用的脚本语言,和Java没什么关系。

Java是解释型还是编译型

先了解下解释型和编译型的定义

定义:

编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。

解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束

区别:

编译型语言,执行速度快、效率高;依靠编译器、跨平台性差些。

解释型语言,执行速度慢、效率低;依靠解释器、跨平台性好。

java是解释型的语言,因为虽然java也需要编译,编译成.class文件,但是并不是机器可以识别的语言,而是字节码,
最终还是需要 jvm的解释,才能在各个平台执行,这同时也是java跨平台的原因。所以可是说java即是编译型的,也是解释型,
但是假如非要归类的话,从概念上的定义,恐怕java应该归到解释型的语言中。

JIT是什么

JIT是Java即时编译器,通过把”热点代码”(即反复被使用的代码)编译成与本地平台相关的机器码,用来提高运行速度

jdk jre jvm是什么,以及它们之间的关系

jdk : jdk是Java开发工具包

jre : jre是Java运行环境。没有jre,java程序就不能运行。

jvm : jvm是java虚拟机,负责执行编译后的.class文件

简单的说:jdk包括jre,jre包括jvm。具体的区别可以看这篇博客

1…567
lichukuan

lichukuan

62 博客
12 分类
© 2019 lichukuan
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4