一、字符串

从概念上讲,java字符串就是Unicode字符串序列。java没有内置的字符串类型,而是在java标准类库中提供了一个String预定义类。每一个用双引号括起来的字符串都是String类的一个实例。

1、子串

astring.substring(start,end);

该方法在astring字符串中提取长度为end-start的字串(不包含end处的字符)

2、字符拼接

(1)用 “+”号拼接:当将一个字符串和一个非字符串类型的值进行拼接hs时,java会自动将后者转化为字符串类型(任何一个java对象都可以转换成字符串,因为有tostring方法)

(2)用String.join();如果BStrin中包含对个字符串(比如说集合)并且A是一定界符,那么String.join(A,BString);可以将BString中所有的子符串通过A定界符拼接起来。

例如:

BString=['a','b','c'];
A=" - ";
String Cstring=String.join(" - ",BString);
//此时Cstring="a - b - c"

3、不可变字符串

优点:编译器可以让字符串共享。(高效)

可以想象java将所有的字符串放在一个公共的储存池中,字符串变量指向储存池中相应的位置,当复制字符串变量时,原始字符串和复制的字符串共享相同的字符。(这有点类似于char*指针,但不能将其视为c的字符串数组)

注意:+ 或者substring等操作产生的结果并不是共享的。

缺点:字符串不能被改变,只能将字符串变量指向另一个字符串。(低效)

要想获得想要的字符串,可以通过提取、拼接等方式得到一个新的字符串,再将fz字符变量指向这个新的字符串。

通常来说,很少需要提取拼接字符串,所以是字符串不可变往往更加高效。

4、监测字符串是否相等

(1)AString.equals(BString);——监测AString字符串和BString字符串是否相等。

其中AString和BString可以字符串变量也可以是字符串的字面值(如:"ABCD")

(2)AString.equalsIgnoreCase(BString);——不区分字符串大小写,监测是否相等

结果为true或者false

注意:

不能使用“==”判断字符串是否相等,它只能判断两个字符串是否放置在内存区域上的同一个位置上,即两个字符串变量是否指向同一个字符。但是完全有可能会出现将内容相同的多个字符串的拷贝放置在不同的位置上。如果虚拟机始终将相同字符串共享,则可以使用“==”判断相等性,但是+或者substring等操作产生的结果并不是共享的。所以不建议使用“==”判断字符串的相等性,以免出现摸不着头脑的错误。

5、空串和null串

(1)空串""是一个长度为零,内容为空的java对象。

可以使用AString.length()==0或者AString.equals("")判断AString是否为空串。

(2)null是个特殊的String变量的值,它表示目前没有任何对象与该变量关联。

可以使用AString==null判断AString是否为null。

6、码点和代码单元

补充:char类型

(1)、码点:是指与一个编码表中的某个字符对应的代码值。在Unicode标准中,码点采用十六进制书写,并加上前缀U+,例如U+0041就是字母A的码点。

(2)、UTF-16编码采用不同长度的编码表示所有的Unicode码点。在基本多语言级别中,每个字符用16位表示,通常被称为代码单元。而辅助字符采用一对连续的代码单元进行编码。

(3)、在java中char类型描述了UTF-16编码中的一个代码单元。

所以java中与码点对应的字符,可能由两个代码单元构成,即可能由两个char类型的值构成。所以,除非确实需要处理UTF-16代码单元,否则尽量将字符串作为抽象数据类型(String)处理,而不使用char类型,因为它太底层。

正文:

如上补充所述:char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元。大多数常用的Unicode字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。

(注意:CharSequence类型是一种接口类型,所有的字符串都属于这个接口)

7、构建字符串

采用字符串的连接方式+或者substring来拼接字符串效率低下,每次拼接字符串都要构建一个新的String对象,既耗时又浪费空间。

解决方法:

使用StringBuilder类可以避免这个问题,代码如下:

//首先创建一个空字符串构建器
StringBuilder builer=new StringBuilder();
//其次每次需要添加新的内容时,就调用append方法
//若ch为单字符变量,则可以添加一个单字符
builder.append(ch);
//如果str是个字符串变量,则可以添加一个字符串
builder.append(str);

//在需要构建字符串时,调用toString方法,便可以得到一个String对象
String AString = builder.toString();

补充:

StringBuffer类是StringBuilder类的前身。两者的api都是相同的。不同之处在于StringBuffer类允许采用多线程的方式执行添加或者删除字符的操作。但是他的效率稍微有些低。所以如果所有的字符串在一个单线程中编辑,则应该使用StringBuilder类型。

 

二、对象和对象变量

1、创建对象

Java中创建类的对象,通过new操作符+类构造器实现

new Date()//创建了一个时间Date类的对象,其中Date()为Date类的构造器

2、使用对象

String s=new Date().toString();
//这个例子创建了一个 Date类的对象 ,并且将 对象 初始化为当前的日期和时间。
//toString方法返回日期的字符串描述,并赋值给s变量

3、对象变量

上述的例子中,构造的对象仅仅使用了一次,通常希望构造的对象可以被多次使用,因此需要将对象放在一个变量中----对象变量。

(1)定义(声明)对象变量

Date nowtime;
//定义了一个对象变量,它可以引用Date类的对象

必须要认识到,变量nowtime不是一个对象,它仅仅被声明为一个Date变量,所以目前实际上也没有引用任何变量。所以不能将类方法一应用于这个变量上。

(2)初始化对象变量

可以通过两种方式初始化这个变量:

//第一种方式:通过新构造的对象初始化这个变量
nowtime=new Date();
//第二种方式:让这个变量引用一个已经存在的对象
//假设 today 是一个已经存在的Date类对象的引用,则可以有:
nowtime=today;

在java中,任何对象变量的值都是对储存在内存区的一个对象的引用,new操作符的返回值也是一个引用。

一定要认识到:一个对象变量并没有实际包含一个对象,而仅仅只是引用了一个对象。(对 对象变量之间进行赋值,实际上结果会是 两个对象变量 共享内存区的同一个对象)

(3)null

可以显示的将对象变量设置为null值,它表示这个对象变量目前没有引用任何对象。

如果将一个方法应用于一个值为null的对象上,则会产生运行时错误(runtime error)

局部变量不会自动地初始化为null,而必须通过调用new或者将他们设置为null进行初始化。

 

【注意】

java对象变量并不是c++中的引用。两者有很大的区别

(1)c++中没有空引用

(2)c++的引用不能被赋值

java对象变量实际上可以视为c++中的对象指针

(1)c++的对象指针必须需要通过new操作符进行创建并初始化

(2)c++中存在空指针

(3)对c++中的对象指针之间进行赋值,则两个指针将指向内存区中的同一个对象。

但是c++的指针很容易出现问题,比如说创建了一个错误的指针、或者造成了内存溢出等等,然而这些问题在Java中都不复存在。

 

三、java类库中的LocalDate类

这是一个用来表示日历表示法的类。而Date类是用来表示时间点的类。

1、创建对象

不要直接使用构造器来构造LocalDate类对象。应当使用静态工厂方法(factory method)代表 你 调用构造器。如:

LocalDate nowtime=LocalDate.now();
//该表达式会自动创建一个新对象并赋值给nowtime,表示构造这个对象时的日期。

LocalDate nowtime=LocalDate.of(2019,5,5);
//该表达式会根据你所提供的日期构造一个特定日期的对象。

LocalDate类的常用方法:

static LocalDate now();//构造一个表示当前日期的对象
static LocalDate of(int year, int month,int day);//构造一个给定日期的对象
int getYear();
int getMonthValue();
int getDayOfMonth();
//获取年月日

DayOfWeek getDayOfWeek();
//得到当前日期是星期几、作为DayOfWeek类的一个实例返回。调用getValu来得到1~7之间的一个数,表示这是星期几,1为周一。

LocalDate pulsDays(int n);
LocalDate minusDays(int n);
//生成当前日期之后或者之前n天的日期

 

四、final实例域、final类、final方法

补充:

(1)由类构造对象的过程称为创建类的实例。对象中的数据称为实例域,操纵数据的过程称为方法。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态。

(2)实现类的封装在于绝对不让其他类直接访问本类中的实例域,程序仅通过对象的方法与对象数据进行交互。所以类的封装又称为数据隐藏。

 

正文:

(1)将实例域定义为final,规定在构建对象时候必须初始化这样的域。即必须确保在每一个构造器执行以后,这个域的值被设置,并且在以后的操作中不会被修改。

(2)final修饰符大都应用于基本数据类型(primitive)的域,或者不可变类的域。(不可变类:类中的每一个方法都不会改变其对象,这种类就是不可变类。)

(3)将类中的方法 声明/定义 为final,则表示子类不能覆盖这个方法。

(4)将类 声明/定义 为final,表示该类不允许被扩展,或者说不允许被继承。

注意:final类中的所有方法将自动的变成final。

将方法或者类声明为final的主要目的是:确保他们不会在子类中改变语义。

 

五、静态域与静态方法

(1)将域定义为static,则表示为静态域。

静态域为类所有,它属于类,为类对象所共享,但是不属于任何独立的类对象。

通常,静态变量使用的较少,静态常量使用的很多。如下例:在类中定义圆周率PI

public class Math{
    public static final double PI=3.1415926;
    
    ···
}

在程序中通常使用 类名.静态常量名 的方式使用该静态常量:如使用Math.PI获得圆周率常量。

如果static关键字被省略,则PI就变成了Math类的一个实例域,需要通过Math类对象访问PI,并且每一个类对象都会有一份独立的PI拷贝。

(2)静态方法是一种不能向对象实施操作的方法。

例如java类库中Math类的pow方法就是一个静态方法。表达式为Math.pow(a,n);z在运算时,不适用任何的Math对象,即该方法没有隐式的参数。

可以认为,静态方法是没有this参数的方法(在一个非静态的方法中,this参数表示这个方法的隐士参数)

静态方法和静态域一样,通常通过类名访问,即:类名.方法名 的形式。也可以使用类对象访问静态方法(即:对象名.方法名 ),但是这种形式容易引起混淆。建议使用类名访问。

在下面的两种情况中可以使用静态方法:

  • 一个方法不需要访问对象的状态,其所需的参数都是通过显示参数提供。(如:Math.pow(a,n); )
  • 一个方法只需要访问类的静态域。(如:Manager.getmanagerName(); )

总结:静态域和静态方法就是属于类但不属于类对象的变量或者方法。

 

六、方法参数

Java程序设计语言总是采用按值调用。即方法得到的是所有参数值的一个拷贝,特别的是,方法不能修改传递给它得任何参数变量得内容。

方法参数有两种类型:

(1)基本数据类型(数字、布尔型)

(2)对象引用

对于对象引用来说,参数传递也是按值传递的。方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

这里有必要说一下【c++中的引用】【注意,c++中的参数的引用调用 和 java中参数的对象引用 不同】

区别:

c++的引用:

c++中的引用实际上是实参的别名,两者代表的是同一个对象或者变量。在函数参数中使用引用参数,则在函数体内对该引用的修改实际上就是对实参的修改。另外:c++中的赋值,是将其他内存空间中的数据拷贝到系统为自己分配的内存空间中,两者的数据是一样的,但是系统为两者分配的区域不是同一个。

java的对象引用:

在java中,任何对象变量的值都是对储存在内存区的一个对象的引用,new操作符的返回值也是一个引用。另外:在同类型的对象之间进行赋值,实际上是使得 对象 在这些对象变量之间共享,这些对象变量的值都是同一个引用,都是指向的同一个对象,即所有对象变量指向内存空间中的同一片区域。

所以,java按值传递实际上传递的是一个对象引用,方法参数获取了这个引用后,就可以指向这个对象。在方法体内,该方法参数可以修改这个对象的数据(或者说这个对象的状态),但是不能改变这个对象在内存区的位置。这只能间接的说明,通过方法参数修改了传递给该方法参数  对象引用  的对象变量所引用的对象变量的状态(因为两者是以同一个对象为纽带联系在一起,所以是间接的)。(这句话有点绕,仔细读一读)

【反例】如果说 将 java的方法参数中的对象引用 理解为引用调用,那就意味着,对方法参数进行操作就是直接对 传递给该参数的对象变量进行操作,很显然,这是个错误的说法。如下例可以很好的证明这一点:

//假设已经定义好了Manager类,并且试图通过下面的swap方法交换Manager类对象
public static void swap(Manager aManager,Manager bMAnager){
    Manager cManager=aManager;
    aManager=bManager;
    bManager=cManager;
}


//通过下面的代码调用swap方法,可以测试该方法能否达到目的:
Manager firstManager=new Manager("lisi");
Manager secondManager=new Manager("zhangsan");
swap(firstManager,secondManager);
/*
 * 显然,在swap方法中,aManager和bManager实现了交换,但是这仅仅是交换了对象引用。
 * 也就是说在方法体内执行了那三条语句后:aManager指向了 原本bManager所引用的对象,而bManager指向了原本aManager所引用的对象。
 * 这就意味着,两个对象在执行swap方法前后没有变化,也就是说,firstManager和secondManager在执行swap方法前后没有变化。
 * 所以这个swap方法并不能交换对像,并不能交换firstManager和secondManager。
 */