Java高级编程学习

1.9日学习笔记

类变量/静态变量

(jdk8以后,静态变量存放在堆里这个类对应的class对象最后,jdk8以前,静态变量存放在方法区)

类变量也叫静态变量/属性,是该类所有对象共享的变量,任何一个该类对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改他时,修改的也是同一个变量。

static类变量,在类加载的时候就生成了。(new的时候加载)即使没有创建对象,只要类加载就可以使用类变量

语法:访问修饰符 static 数据类型 变量名;

如何访问类变量:类名.类变量名(推荐) 或 对象名.类变量名(静态变量的访问修饰符的访问权限和范围和普通属性是一样的)

当需要让某个类所有对象都共享一个变量时,就可以考虑使用类变量

类变量是该类的所有对象共享的,而实例变量是每个对象独享的。

加上static称为类变量或静态变量,否则称为实例变量/普通成员变量/非静态变量/普通属性

实例变量不能通过:类名.类变量名访问。

类变量的生命周期随类的加载开始,伴随类的消亡毁灭

当方法使用了static修饰后,该方法就是静态方法,只能访问静态属性/变量

类方法和普通方法都是随类的加载而加载,将结构信息存储在方法区:类方法中不允许使用和对象有关的关键字如:super和this关键字。

普通方法中隐含着this参数

普通方法和对象有关,需要通过对象名调用,不能通过类名调用。

普通方法既可以访问非静态成员也可以访问静态成员。

Main方法

main方法java虚拟机调用

jvm虚拟机需要调用类的main方法,所以该方法的访问权限必须是public

java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static

该方法接受String类型的数组参数,该数组中保存执行Java命令时,传递给所运行的类的参数

在main方法中,可以直接调用main方法所在类的静态方法或静态属性。但是不能直接访问该类的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。

代码块

代码化块又称为初始化块,属于类中的成员(即是类的一部分)类似于方法,讲逻辑语句封装在方法体中,通过{}包围起来。但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。

语法:[修饰符]{代码};

修饰符可选,要写的话也只能写static;代码块分两类,一类时用static修视的叫静态代码块;一类没有修饰符,叫普通代码块;逻辑语句可以为任何(输入、输出、方法调用、循环、判断等);其中 " ;" 可以写也可以省略;

相当于是另外一种形式的构造器(对构造器的补充机制)可以做初始化的操作;如果多个构造器中都有重复的语句,可以抽取到初始代码块中,提高代码的重用性。

当不管调用哪个构造器创建对象,都会先调用代码块的内容。

static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行1次。如果是普通代码块,每创建1个对象,就会执行1次。

普通代码块,在创建对象时会被隐式的调用。如果只是使用类的静态成员时,普通代码块不会执行。(普通代码块是构造器的补充)

静态代码块先于构造器加载。

创建一个对象时,在一个类调用的顺序

  1. 调用静态代码块和静态属性初始化(静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
  2. 调用普通代码块和普通属性的初始化(普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
  3. 调用构造方法

构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕。因此优先于构造器和普通代码块执行的

创建一个子类时,他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序

  1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  4. 父类的构造方法
  5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  6. 子类的构造方法

静态代码块只能调用静态成员(静态属性和方法),普通代码块可以调用任意成员

类的加载

创建对象实例时;创建子类对象实例时,父类也会被加载;使用类的静态成员时(静态属性、方法);都会执行代码块。

单例设计模式

采取一定的方法保证在整个的软件系统中,对一个类只能存在一个对象实例,并且该类只提供1个取得其对象实例的方法。

单例模式有两种方式:1、饿汉式(有可能没使用到对象但是随类的加载会被创建(一开始就定义static 对象名=new 类名()));2、懒汉式(在程序运行过程中,只能创建一个对象,先定义一个静态static对象,但不初始化;在定义一个公共静态方法,初始化并返回一个对象);

步骤:1、将构造器私有化->防止直接new;2、类的内部创建对象;3、向外暴露一个静态的公共方法(getInstance(),返回对象)

饿汉式和懒汉式:

  • 主要区别在于创建对象时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
  • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
  • 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。

Runtime就是典型的单例模式

Final关键字

final可以修饰类、属性、方法、局部变量。

当不希望类被继承,可以用final修饰;当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰;当不希望类的某个属性的值被修改时,可以用final修视;当不希望某个局部变量被修改时,可以使用final修饰。

final修饰的属性又叫常量,一般 用xx_xx_xx来命名;

final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一选择一个:(定义时,在构造器中,在代码块中)

如果final修饰的属性时静态的,则初始化的位置只能是:(定义时,在静态代码块 )不能在构造器中赋值

final类不能继承,但是可以实例化对象

如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承

一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。

final不能修饰构造方法。

final往往与static搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理

包装类(Integer、Double、Float、Boolean等)、String都是final类。

抽象类

当父类的某些方法需要声明,但是有不确定如何实现,可以用abstract关键字将其声明为抽象方法,那么用abstract来修饰该类就是抽象类。

父类的抽象方法,子类实现。

语法:访问修饰符 abstract 类名{}

用abstract关键字修饰一个方法时,这个方法就是抽象方法。语法:访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体

抽象类的价值更多在于设计,设计者设计好后,让子类继承并实现抽象类

抽象类不能实例化

抽象类不一定要包含abstract方法,也就是说抽象类也可以没有abstract方法

一旦类包含了abstract方法,则这个类必须声明为abstract。abstract只能修饰类和方法,不能修饰属性和其他的。

抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法、构造器、静态属性等

抽象方法不能有主体;

如果一个类继承了抽象类,则它必须实现抽象类的抽象方法,除非它自己也声明为abstract类。

抽象方法不能使用private、final、static来修饰,因为这些关键字都是和重写相违背。

模板设计模式

写一个模板类,再写其他子类继承该模板类。

接口

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。 

语法: interface 接口名{ //属性    //方法}

class 类名 implements 接口{ //自己属性;   //自己方法;  // 必须实现的接口的抽象方法}

jdk7.0以前 接口里的所有方法都没有方法体,即都是抽象方法。jdk8.0以后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。

接口不能被实例化,接口中所有的方法是public方法,接口中抽象方法可以不用abstract修饰,public也可以不用修饰。

一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alter+enter来解决

抽象类去实现接口时,可以不实现接口的抽象方法。

一个类可以同时实现多个接口,接口中的属性只能是final的,而且是public static final 修饰符

接口中属性的访问形式: 接口名. 属性名

一个接口不能继承其他的类,但是可以继承多个别的接口

接口的修饰符,只能是public和默认

普通类实现接口时,就拥有了接口类的属性和方法(类似继承)

  • 继承的价值:解决代码的复用性和可维护性
  • 接口的价值:设计,设计好各种规范方法,让其它类去实现这些方法
  • 接口比继承更加灵活,继承是满足 is-a 的关系,而接口只需满足 like -a 的关系
  • 接口在一定程度上实现代码解耦(即:接口规范性+动态绑定)

接口的多态特性:

多态参数,多态数组,接口存在多态传递现象:接口类型的变量可以指向实现了该接口的类的对象的实例。

若出现继承的父类和实现的接口有相同的属性,则要明确指出访问的是哪个(super.属性 或 接口名.属性)

内部类

一个类的内部又完整嵌套了另一个类结构。被嵌套的类成为内部类,嵌套其他类的类称为外部类。内部类的最大特点就是:可以直接访问私有属性,并且可以体现类与类之间的包含关系

语法:class Outer{ class Inner{} }

类的五大成员:属性、方法、构造器、代码块、内部类

内部类的分类:

定义在外部类局部位置上(比如方法内:)

局部内部类(有类名):

  • 可以直接访问外部类的所有成员,包含私有的;
  • 不能添加访问修饰符,因为他的地位就是一个局部变量;
  • 局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final;
  • 作用域:仅仅再定义它的方法或代码块中)
  • 局部内部类->访问->外部类成员:访问方式:直接访问
  • 外部类->访问->局部内部类的成员:访问方式:外部类在方法体中创建对象,然后调用方法(必须在作用域内)
  • 外部其他类->-不能访问局部内部类(因为局部内部类地位是一个局部变量)
  • 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员去访问)

匿名内部类

匿名内部类定义在外部类的局部位置,比如方法体中,并且没有类名(本质是类,内部类,没名字,同时还是一个对象)

  • 语法: new 类/接口 (参数列表){类体};
  • 当作实参直接传递
  • 定义在外部类的成员位置上:

成员内部类(没用static修饰)

成员内部类定义在外部类的成员位置,并且没有static修饰:

可以直接访问外部类的所有成员,包含私有的

可以添加任意访问修饰符(public、protected、默认、private)因为它的地位就是一个成员

作用域:和外部类的其他成员一样,为整个类体,在外部类的成员方法中创建成员内部类对象,在调用方法

如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问

成员内部类->访问->外部类,访问方式:直接访问

外部类->访问->成员内部类,访问方式:创建成员内部类的对象,再访问

外部其他类->访问->成员内部类的方式:1、外部类的对象 new内部类的实例(外部类. new 内部类()相当于把new 内部类()当作外部类的成员)2、在外部类中编写一个方法,返回内部类对象

静态内部类

静态内部类是定义在外部类的成员位置,并且有static修饰

可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

可以添加任意访问修饰符(public、protected、默认\private)因为它的地位就是一个成员。

作用域:同其他的成员,为整个类体。

外部其他类使用静态内部类:1、因为静态内部类可以通过类名直接访问(前提满足访问权限)2、编写一个方法返回静态内部类的对象实例

如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。

枚举

枚举对应英文enum,枚举是一组常量的组合(枚举属于一种特殊的类,里面只包含一组有限的特定的对象)

自定义枚举类:

1、将构造器私有化,目的:防止直接new

2、去掉setXX方法,防止属性被修改(因为枚举对象通常为只读),可以提供Get方法。

3、在类内部,直接创建固定的对象

4、优化,可以加入final修饰符(final static共同修饰)

枚举对象名通常使用全部大写,常量的命名规范。

枚举对象根据需要,也可以有多个属性。

使用enum实现枚举类:

1、使用关键字enum替代class
2、public static final 类名 对象名 = new 类名(参数)直接使用 常量名(实参列表)【作为构造器】

3、如果有多个常量(枚举对象),使用逗号' , '间隔即可,最后有一个分号结尾。

4、如果使用enum来实现枚举,要求将定义常量对象,写在前面(枚举类的行首)。

使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类

枚举类定义的时候,必须知道调用的是哪个构造器。

如果使用无参构造器创建枚举类型,则实参列表和小括号都可以省略。

toString方法:返回当前对象的名,子类可以重写给该方法,用于返回对象的属性信息

name:返回当前对象名(常量名),子类中不能重写

ordinal:返回当前对象的位置号,默认从0开始

values:返回当前枚举类中所有的常量

valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!

compareTo:比较两个枚举常量,比较的就是位置号。

enum实现接口

使用enum关键字后就不能再继承其他类了,因为enum会隐式继承Enum,而java是单继承制。

枚举类和普通类一样,可以实现接口:语法:enum 类名 Implements 接口1,接口2 { }

增强For循环

语法:for(元素类型 元素名:集合名或数组名){访问元素}    //ElementType元素类型,element 元素名,arrayName数组名。 只能用于遍历集合或数组。

注解

注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息

和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。

再JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。再JavaEE中注解占据了更重要角色,例如用来配置应用程序的任何切面,代替Java EE旧版中所一六的繁冗代码和XML配置等

使用Annotation时要在前面增加@符号,并把该Annotation当成一个修饰符使用。用于修饰它支持的程序元素。

三个基本的Annotation:

1、@Override:

限定某个方法,是重写父类方法(如果写了,编译器就会去检查该方法是否真的重新给了父类的方法;

如果没有构成重写,则编译错误。),该注解只能用于方法,不能修饰其他类、包、属性等;

查看@Override注解源码为:@Target(ElementType.METHOD),说明只能修饰方法;

@Target是修饰注解的注解,称为元注解。

@interface的说明:@interface 不是interface,是注解类 是jdk5.0之后加入的(public @interface Override {} 注解类)

2、@Deprecated:

用于表示某个程序元素(类、方法等)已过时;即不再推荐使用,但是仍然可以使用。

@Target(value={CONSTRUCTOR,FIELD,LOCAL_VARIABLE.METHOD,PACKAGE,PARAMETER,TYPE})

@Deprecated的作用可以做到新旧版本的兼容和过渡

3、@SuppressWarnings:

抑制编译器警告,在@SuppressWarnings({“”}),在({“”})中可以填入你希望抑制(不显示)警告信息。

元注解

JDK的元注解用于修饰其他的注解;

元注解的种类:

Retention

//指定注解的作用范围(只能修饰一个注解的定义,用于指定该注解可以保留多长时间;

@Retention包含一个RetentionPolicy类型的成员变量,使用@Retention时必须为该value成员变量指定值)

SOURCE(编译器使用后,直接丢弃这种策略的注释,不会写入.class文件也不会再runtime过程中生效),

CLASS(编译器把注释记录在class文件中,当运行JAVA程序时,JVM不会保留注解;默认值)

RUNTIME(编译器把注释记录在class文件中,当运行Java程序时,JVM会保留注释,程序可以通过反射获取该注释)

Target

//指定注解可以在那些地方使用,用于指定被修饰的注解能用于修饰哪些程序元素。@Target也包含一个名为value的成员变量

Documented

//指定该注解是否会在javadoc体现,用于指定被该元注解修饰的注解类将被javadoc工具提取成文档,即在生成文档时,可以看到该注解。

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

inherited

//子类会继承父类注解,如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解(实际应用中,使用较少)

异常

java语言中:将程序执行中发生的不正常情况称为异常。(开发过程中的语法错误和逻辑错误不是异常)

使用异常处理机制对异常进行捕获,保证程序可以继续运行。‘

将代码块选中->快捷键: ctrl+alt+t ->   try-catch

如果进行异常处理,那么即使出现了异常,程序可以继续执行

执行过程中所发生的异常事件可分为两类:

Error错误:Java虚拟机无法解决的严重问题,程序会崩溃。如:JVM系统内部错误,资源耗尽等。StackOverFlowError(栈溢出)和OOM(out of memory)

Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。有:运行时异常(程序运行时,发生的异常)和编译时异常(编程时,编译器检查出的异常)。如空指针访问,试图读取不存在的文件,网络连接中断。

常见运行时异常:

  • NullPointerException空指针异常
  • ArithmeticException数学运算异常
  • ArrayIndexOutOfBoundsException数组下标越界异常
  • ClassCastException类型转换异常
  • NumberFormatException数字格式不正确异常

编译异常:

  • SQLException操作数据库时,查询表可能发生异常
  • IOException操作文件时,发生的异常
  • FileNotFoundException当操作一个不存在的文件时,发生异常
  • ClassNotFoundException加载类,而该类不存在时,发生异常
  • EoFException操作文件,到文件末尾,发生异常
  • IllegalArguementException参数异常

异常处理

1、try-catch-finally:程序员在代码中捕获发生的异常,自行处理

try{//代码可能有异常

}catch(Exception e){//捕获到异常,当异常发生时,系统将异常封装成Exception对象e传递给catch

}finally{//不管try代码块是否有异常发生,始终都要执行finally;如果没有发生异常,catch代码块不执行

}

根据程序需要在程序中可能有多个try-catch块。可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前。(比如Exception在后,NullPointerException在前,如果发生异常,只会匹配一个catch)try{}catch(NullPointerException e){} catch(Exception e){} finally{}

如果没有finally,可以编译通过

如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块。如果有finally,最后还需要执行finally里面的语句。

2、throws:将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM

如果一个方法中的语句执行时,可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表示该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中的异常类型,也可以是它的父类。

throws关键字后也可以是 异常列表,即可以抛出多个异常

对于编译异常,程序中必须处理(比如:try-catch或者throws)

对于运行时异常,程序中如果没有处理,默认就是throws方式处理。

子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型

在throws过程中,如果有方法Try-catch,就相当于处理异常,就可以不必throws

自定义异常

自定义异常的步骤:

  • 定义类:自定义异常类名,继承Exception或RuntimeException
  • 如果继承Exception,属于编译异常
  • 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)

throw和throws

throws 异常处理的一种方式,位置处于方法声明处,后面跟异常类型

throw 手动生成异常对象的关键字,位置处于方法体中,后面跟异常对象

包装类

针对八种基本数据类型相应的引用类型-包装类,有了类的特点,就可以调用类中的方法。

jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,反之,拆箱

jdk5以后(含jdk5)的自动装箱和拆箱方式

自动装箱底层调用的是valueOf方法。

手动装箱:基本数据类型转换成包装类:(eg: int n1=100; Integer integer= new Integer(n1); 或 Integer interger=Integer.valueOf(n1);)

手动拆箱:int i = integer.intValue();

自动装箱:int -> Integer: Integer integer=n1;//底层使用 Integer.valueOf(n1);

自动拆箱:int n3=integer;//底层仍然使用intValue()方法。

Object obj1=true? new Integer(1):new Double(2.0);System.out.println(obj1);输出1.0,因为类型会自动转为Double

包装类型和String类型相互转换

1、Integer i=100;//自动装箱;

//方式1:String str= i+"";//方式2:String str=i.toString();//方式3:String str3=String.valueOf(i);

String转包装类:String str="12345";

//方式1:使用自动装箱:Integer integer=Integer.parseInt(str4);

//方式2:Integer integer=new Integer(str4);//构造器

Integer创建机制

Integer.valueOf()底层源码:1、判断整性i变量是否在-128到127之间,若在,则直接返回变量i;2、若不在,则返回整性Integer(i)对象

String类

1、String对象用于保存字符串,也就是一组字符序列;2、字符串常量对象是用双引号扩起的字符序列;3、字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占2个字节;

4、String类较常用构造方法:

  • String s1="hsp"; 先从常量池查看是否有"hsp"数据空间,如果有直接指向,如果没有则重新创建然后指向。s1最终指向的是常量池的空间地址
  • String s2=new String("hsp");先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。如果常量池没有hsp,重新创建,如果有,直接通过value指向。s2最终指向的是堆中的空间地址。
  • String s3=new String(char [] a);
  • String s4=new String(char [] a, int startIndex, int count);

String实现了Serializable:说明字符串可以串行化,可以在网络上传输。

String实现了Comparable:说明String对象可以相互比较。

String是Final类,不能被其他类继承,代表不可变的字符序列。

字符串本质是char数组,String有属性private final char value[];用于存放字符串内容,value是final类型,不可修改。(地址不可修改,内容可以修改)字符串是不可变的,一个字符串对象一旦被分配,其内容不可变。

当调用intern方法时,如果池已经包含一个等于此String对象的字符串(用equals[Object]方法确定),则返回池中的字符串,否则,将此String对象添加到池中,并返回此String对象的引用。(即intern()方法最终返回的是常量池的地址(对象));

String a="hello";//创建a对象

String b="abc";//创建b对象

String c=a+b;//底层:1、StringBuilder sb=new StringBuilder(); sb.append(a);sb.append(b);sb在堆中,并且append是在原来字符串的基础上追加的。

String c1="ab"+"cd";常量相加,看的是池。String c1=a+b;变量相加,是在堆中。

数组默认存放在堆中

String类常见方法

  • equals//区分大小写,判断内容是否相等
  • euqalsIgnoreCase//忽略大小写的判断内容是否相等
  • length//获取字符个数,字符串长度
  • indexOf//获取字符在字符串中第一次出现的索引,索引从0开始,如果找不到返回-1
  • lastIndexOf//获取字符在字符串中最后1次出现的索引,索引从0开始,如果找不到返回-1
  • substring//截取指定范围的子串
  • trim//去前后空格
  • charAt//获取某索引处的字符,注意不能使用Str[index]方式
  • toUpperCase//大写
  • toLowerCase//小写
  • concat//拼接
  • replace//替换字符串中的字符
  • split//分割字符串,返回一个数组。对于某些分割字符,我们需要转义(比如| \\等)
  • compareTo//比较两个字符串大小 (1)长度相同:并且每个字符也相同返回0(2)如果长度相同或不同,但是进行比较时可以区分大小,就返回c1-c2(3)如果前面的部分都相同,就返回str1.len-str2.len
  • toCharArray//转换成字符数组
  • format//格式字符串,%s字符串,%c字符,%d整性,%.2f浮点型

StringBuffer类

java.lang.StringBuffer代表可变的字符列表,可以对字符串内容进行增删。很多方法与String相同,但StringBuffer长度可变。StringBuffer是一个容器。

StringBuffer直接父类是AbstractStringBuilder,StringBuffer实现了Serializable,即StringBuffer对象可以串行化。在父类AbstractStringBuilder中有属性char []value,但是value不是final类型。该value数组存放字符串内容(存放在堆中)

StringBuffer是final类,不可被继承。每次StringBuffer的更新实际上是可以更新内容,不用每次更新地址,效率高。(只有当数组空间不够,才切换地址)

StringBuffer构造初始容量是16个字符。StringBuffer stringbuffer=new StringBuffer();

StringBuffer stringbuffer=new StringBuffer(100);//指定容量

StringBuffer stringbuffer=new StringBuffer("hello");//创建当前字符串长度+16的空间大小,再将字符串拼接。

StringBuffer类常用方法

  • 增append
  • 删delete(start,end)
  • 改replace(start,end,string)//将start---end间的内容替换掉,不含end
  • 查indexOf//查找子串再字符串中第一次出现的索引,索引从0开始,如果找不到返回-1
  • 插insert
  • 获取长度length

String str=null; StringBuffer sb=new StringBuffer(); sb.append(str);//底层调用AbstractStringBuilder的appendNull,将value中赋值'n','u','l','l';

System.out.println(sb.length());//输出4

若:StringBuffer sb1=new StringBuffer(str);//看底层源码:super(str.length()+16);会抛出空指针异常,因为str为Null

String和StringBuffer转换:

String转StringBuffer:String s1="hello";//方式1:StringBuffer b1=new StringBuffer(s1);

//方式2: StringBuffer b2=new StringBuffer(); b2.append(s1);

StringBuffer转String://方式1:String s1=b1.toString(); //方式2:String s3=new String(b1);

StringBuilder类

可变的字符序列,此类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,简易优先采用该类,因为在大多数实现中,它比StringBuffer类要快。

在StringBuilder上面的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。

StringBuilder继承AbstractStringBuilder类,实现了Serializable,说明StringBuilder对象可以串行化(对象可以网络传输,可以保存到文件)StringBuilder是final类,不能被继承。StringBuilder对象序列仍然存放在其父类AbstractStringBuilder类的char [] value;因此,字符序列是堆里面的。StringBuilder的方法,没有做互斥处理,即没有synchronized关键字,因此在单线程情况下使用

Math类

均为静态方法。

  • abs绝对值
  • pow求幂
  • ceil向上取整
  • floor向下取整
  • round四舍五入
  • sqrt求开方
  • random求随机数
  • max求两个数最大值
  • min求两个数最小值

Arrays类

用于管理和操作数组;

  • toString返回数组的字符串形式 Arrays.toString(arr)
  • sort排序(自然排序和定制排序)
  • binarySearch二分搜索查找,要求必须排好序
  • copyOf数组元素复制 Integer[] newArr=Arrays.copyOf(arr,arr.length);
  • fill数组元素填充 Integer []num=new Integer[]{9,3,2}; Arrays.fill(num,99);
  • equals比较两个数组元素内容是否完全一致 boolean equals=Arrays.equals(arr,arr2);
  • asList将一组值转换成list List<Integer> asList=Arrays.asList(2,3,4,5,6,1);

System类

  • exit退出当前程序
  • arraycopy复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组
  • currentTimeMillens返回当前时间距离1970-1-1的毫秒数
  • gc运行垃圾回收机制System.gc();

大数处理

BigInteger适合保存比较大的整形 BigInteger bigInteger=new BigInteger("88826363554020027");

BigDecimal适合保存精度更高的浮点型(小数)

常见方法:在堆BigInteger进行加减乘除要使用对应的方法

  • add加
  • subtract减
  • multiply乘
  • divide除

日期类

第一代日期类:

Date:精确到毫秒,代表特定的瞬间

SimpleDateFormat:格式和解析日期的类,允许进行格式化(日期->文本)、解析(文本->日期)和规范化

1、创建SimpleDateFormat对象,可以指定相应的格式

2、这里的格式使用的字母是规定好,不能乱写

Date d1=new Date();

SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");

String format=sdf.format(d1);//format:将日期转换成指定格式的字符串

可以把一个格式化的String,转成相应的Date:String s="1996年01月01日 10:20:30 星期一"

Date parse=sdf.parse(s);

第二代日期类:

主要就是Calendar类(日历): public abstract class Calendar extends Object implements Serializable, Cloneable, Comparable

Calendar类是一个抽象类,并且构造器是Private,它为特定瞬间与一组诸如YEAR,MONTH,DAY_OF_MONTH,HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法。

可以通过getInstance来获取实例。Calendar c=Calendar.getInstance();//创建日历类对象

获取日历对象的某个字段:

  • c.get(Calendar.YEAR);
  • c.get(Calendar.MONTH)+1;因为返回月时,时按照0开始编号。
  • c.get(Calendar.DAY_OF_MONTH);
  • c.get(Calendar.HOUR);
  • c.get(Calendar.SECOND);

存在问题:

  • 1、可变性:像日期和时间这样的类应该是不可变的
  • 2、偏移性:Date中的年份时从1900开始,而月份都是从0开始
  • 3、格式化:格式化只对Date有用,Calendar不行。
  • 4、线程不安全,不能处理闰秒(每隔2天多出1s)

第三类日期类:(jdk8加入)

1、LocalDate(只包含日期/年月日,可以获取日期字段)、LocalTime(只包含时间/时分秒,可以获取时间字段)、LocalDateTime(包含日期+时间/年月日时分秒,可以获取日期和时间字段)

2、DateTimeFormatter格式日期类,类似于SimpleDateFormat:

DateTimeFormat dtf=DateTimeFormatter.ofPattern(格式);String str=dtf.format(日期对象);

使用now()返回表示当前日期时间的对象

LocalDateTime ldt=LocalDateTime.now();

DateTimeFormatter datetimeformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

String format=dateTimeFormatter.format(ldt);

3、Instance时间戳

提供了一系列和Date类转换的方式:Date date=Date.from(instant);

Instant instant=date.toInstant();

4、第三代日期类更多方法:

LocalDateTime类,MonthDay类:检查重复事件;是否是闰年,增加日期的某个部分,使用plus方法测试增加时间的某个部分;使用minus方法测试查看一年前和一年后的日期

集合

数组长度开始时必须指定,而且一旦指定,不能更改;保存的必须为同一类型的元素;使用数组进行增加元素的代码比较麻烦。

集合:可以动态保存任意多个对象,使用比较方便。提供了一系列方便的操作对象的方法:add、remove、set、get等;使用集合添加,删除新元素的代码简洁。

集合主要有两大类:

集合主要是两组(单列集合和双列集合)

Collection接口有两个重要的子接口 List 和Set,他们实现的子类都是单列集合

 Map接口的实现子类是双列集合,存放的是K-V

Collection接口和常用方法

public interface Collection<E> extends Iterable <E>

  • 1、collection实现子类可以存放多个元素,每个元素可以是Object
  • 2、有些collection的实现类,有些是有序的List,有些不是有序Set
  • 3、Collection接口没有直接地实现子类,是通过他的子接口Set和List来实现的
  • 4、有些collection的实现类,可以存放重复的元素,有些不可以
  • add添加单个元素
  • remove删除指定元素
  • contains查找元素是否存在
  • size获取元素个数
  • isEmpty判断是否为空
  • clear清空
  • addAll添加多个元素
  • containsAll查找多个元素是否都存在
  • removeAll删除多个元素

Iterator迭代器

Iterator对象成为迭代器,主要用于遍历Collection集合中的元素。所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。

Iterator仅用于遍历集合,Iterator本身并不存放对象。

Collection coll=new ArrayList();

Iterator iterator=coll.iterator();得到一个集合的迭代器

hasNext();判断是否还有下一个元素

while(iterator.hasNext()){}

iterator.next()//指针下移,将下移以后集合位置上的元素返回。

在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用It.next()会抛出NoSuchElementException异常

快捷键:生成while -> itit

List接口

1、List接口是Collection接口的子接口,List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

2、List集合中的每个元素都有其对应的顺序索引,即支持索引。

3、List容器中的每个元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

4、JDK API中List接口的实现类有:ArrayList、LinkList和Vector

List接口和常用方法

  • void add(int index, Object ele)在index位置插入ele元素
  • boolean addAll(int index,Collection eles)从index位置开始将eles中所有元素添加进来
  • Object get(int index)获取指定index位置的元素
  • int indexOf(Onject obj)返回obj在集合中首次出现的位置
  • int lastIndexOf(Object obj)返回obj在当前集合中末次出现的位置
  • Object remove(int index)移除指定index位置的元素,并返回此元素
  • OBject set(int index, Object ele)设置指定index位置的元素为ele,相当于替换
  • List subList(int fromIndex,int toIndex)返回从fromIndex到toIndex位置的子集合

permits all elements,including null,ArrayList可以加入null,并且多个

ArrayList

是由数组来实现数据存储的

ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrayList

ArrayList中维护了一个Object类型的数组elementData。transient Object []:表示不会被序列化

当创建ArrayList对象是,若使用的是无参构造器,则初视elementData容量为0,第一次添加时,则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍。

如果使用的是指定大小构造器,则初始elementData容量为指定大小,如需再次扩容,则扩容elementData为1.5倍。

LinkedList

实现了双线链表和双端队列,可以添加任意元素,线程不安全,没有实现同步

LinkedList底层维护了一个双向链表,LinkedList中维护了两个属性first和last分别指向首节点和尾节点。每个节点Node对象,里面又维护了prev,next,item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表。LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

如果我们改查的操作多,选择ArrayList。如果我们增删的操作多,选择LinkedList.一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList.

Set接口和常用方法

1、无序(添加和取出的顺序不一致),没有索引。2、不允许重复元素,所以最多包含一个null

和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection一样。

Set接口的遍历方式:

同Collection的遍历方式一样,因为Set接口时Collection接口的子接口。(可以使用迭代器,增强for,不能使用索引(普通的for循环)的方式来获取

HashSet

实现了Set接口,实际上是HashMap(HashMap底层是:数组+链表+红黑树),可以存放null值,但是只能有一个Null。HashSet不保证元素是有序的,取决于hash后,再确定索引的结果。不能有重复元素/对象。

添加一个元素时,先得到hash值,会转成索引值。

找到存储数据表table,看这个索引表位置是否已经存放元素,如果没有直接加入。

如果有,调用equals比较,如果相同则放弃添加,如果不同,则添加到最后。

在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树),否则仍然采用数组扩容机制。

LinkedHashSet

LinkedHashSet是Set子类。底层是LinkedHashMap,底层维护了一个数组+双向链表。

LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。LinkedHashSet不允许添加重复元素。

Map接口

Map与Collection并列存在,用于保存具有映射关系的数据。Map中的key和value可以是任何引用类型的数据,会封装到HashMap的对象中。Map中的key不可以重复,value可以重复,key可以是Null(但只能有1个),value也可以是null。常用的String类可以作为Map的key

key和value之间存在单项一对一关系,即通过指定的key总能找到对应的value(Node实现了Entry接口)

遍历:1、取出所有的key,通过key取出对应的value(1、增强for;2、迭代器;3、通过EntrySet)

HashTable

存放的是键值对(K-V),键和值都不能为null,否则会抛出NullPoint。线程安全,hashMap线程不安全。

Properties

继承Hashtable类并实现了Map接口,也是使用一种键值对的形式来保存数据,还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改。

如何选择集合实现类

1、先判断存储类型(一组对象或一组键值对)

2、一组对象:Collection接口

        允许重复:List :

                        增删多:LinkedList(底层维护了一个双向链表);

                        改查多:ArrayList(底层维护了Object类型的可变数组)

        不允许重复:Set:

                        无序HashSet(底层是HashMap,维护了一个哈希表(即数组+链表+红黑树));       

                        排序:TreeSet

                        插入和取出顺序一致:LinkedHashSet,维护数组+双向链表

3、一组键值对:Map

           键无序:HashMap(底层是哈希表,jdk7:数组+链表;jdk8:数组+链表+红黑树)

            键排序:TreeMap

            键插入和取出顺序一致:LinkedHashMap

            读取文件:Properties

Collections工具类

Collections是一个操作Set、List、Map等集合的工具类,提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

  • reverse(List):反转List中元素的顺序
  • shuffle(List):对List集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定List集合元素按升序排序
  • sort(List,Compareator):根据指定的Comparator产生的顺序对List集合元素进行排序
  • swap(List,int,int):将指定list集合中i处元素和j处元素进行交换。
  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
  • Object min(Collection):
  • Object min(Collection, Comparator):
  • int frequency(Collection,Object):返回指定集合中指定元素出现的次数
  • void copy(List dest,List src):将src中的内容复制到dest中
  • boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象中的所有旧值

泛型

能对加入到集合ArrayList中的数据类型进行约束,遍历的时候,需要进行类型转换

ArrayList<Dog>表示存放到ArrayList集合中的元素是Dog类型。如果编译器发现添加的类型不满足要求就会报错。

泛型又称参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题。在类声明或实例化时只要指出好需要的具体的类型即可。

java泛型可以保证程序在编译时没有发出警告,则运行时就不会产生ClassCastException异常,同时,代码更加简洁、健壮。

泛型的作用是可以在类声明时通过一个标识符表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型

interface 接口<T/E>{}和class 类<K,V>{}//其中K,V不代表值,而是表示类型,任意字母都可以。常用T表示,是Type的缩写。T和E只能是引用类型

泛型的实例化:List<String> strList=new ArrayList<String>9);

在给泛型指定具体类型后,可以传入该类型或者其子类类型。(默认E是Object类型)

反省不具备继承性

<?>支持任意泛型类型

<? extends A>:支持A类以及A类的子类,规定了泛型的上限

<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限

自定义泛型

自定义泛型类:

class 类名<T,R...>{

//成员

}

普通成员可以使用泛型(属性、方法)。使用泛型的数组,不能初始化。静态方法中不能使用类的泛型。泛型类的类型,实在创建对象时确定的(因为创建对象时,要指定类型)

如果在创建对象时,没有指定类型,则默认为Object

自定义泛型接口:

interface 接口名<T,R...>{}

接口中,静态成员不能使用泛型。泛型接口的类型在继承接口或实现接口时确定。

没有指定类型,则默认为Object

自定义泛型方法:

修饰符<T,R...>返回类型 方法名(参数列表){}

泛型方法可以定义在普通类中,也可以定义在泛型类中。当泛型方法被调用时,类型会确定。

public void eat(E e){}修饰符后没有<T,R> eat方法不是泛型方法,而是使用了泛型。


版权声明:本文为weixin_43892090原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。