找工作失败发现了自己的问题之后,觉得还是基础比较差。所以重新开始,用将近半个月时间从SE部分把知识重新过了一遍,总结记录一下整个过程中有价值的问题和一些注意事项,面试应该也用得到:
有几个地方没有自动换行,自己在逗号的地方随意回车了,看的时候不知道有没有障碍。
这算是编辑器bug吧?
1、数据类型
基本数据类型8种:
数字型byte、short、int、long,默认为int型
浮点型float、double,默认为double
字符型char,布尔型boolean
引用类型:String等。
注意事项:
这里会经常被问到String是不是基本数据类型,由于使用方法平时与基本类型很相近,容易混淆
声明float类型直接赋值时,数字后面要记得加f,因为默认浮点数是double,需要进行转换。同理声明long时,数字后加L
例如:float f = 12.345f; long l = 1000000000000000L;
自动转型时顺序:byte,short,char->int->long->float->double
向下转型时需要强制转换,但需要注意丢失精度问题
例:byte b1=3,b2=4,b3;
b3=b1+b2;//error,变量相加,类型提升为int,不能直接赋值给b3
b3=3+4;//这样就没问题,计算后在byte范围内
另外,byte范围是-128~127
如果强制赋值byte b = (byte) 130;根据补码截取计算,b=-126;
2、运算符以及各种输出
(1)加号+在数字之间为运算符,但如果先出现过字符串,则变成了连接符,这里经常会有输出的问题
例:
System.out.println(5 + 5 + "5");//这里输出为105
System.out.println("5" + 5 + 5);//这里输出则是555
第一行输出在碰到“5”之前,+还是运算符,直接计算5+5
而第二行从一开始+就成了连接符,每个元素直接按字符串拼接。
(2)结合前面赋值类型问题,这里还有一个需要注意的事
例:short s = 1;
s = s + 1;与s += 1;
跟上面一个例子一样,左边s+1被认定为int型,不能直接赋值给short型
而右边就没有问题,因为+=符号隐含了一个强制类型转换,相当于s = (short) (s+1);。
(3)^符号,同0异1,根据这个特性可以知道一个数据对另一个数据异或两次,结果不变
例子:实现两个变量交换
这里当然不是最简单的通过一个中间值tmp来实现交换,而是采用^符号
x = x^y;//
y = x^y;//这里y = x ^ y ^ y;异或两次返回x赋值给了y
x = x^y;//x = x ^ y ^ x;返回y赋值给了x实现交换
注:还可以通过运算符优先级来一句话实现 y = (x + y) - (x = y);
(4)位移运算符
<<:左移右边补0,如果计算值,可以看做乘以2的移动次幂
>>:右移为除以2的移动次幂
>>>:无符号右移,因为>>右移时最高位是什么就补什么,而这个就是补0
例:最高效率求2*8
2<<3;//相当于2*(2^3);
3、流程控制语句
顺序、选择、循环
这里就是编程语言都通用的部分,很基础的联系了一些简单算法
4、数组
初始化:
动态初始化 数组类型[] 数组名 = new 数组类型[长度];
静态初始化 数组类型[] 数组名= { , ,};
直接以数组名输出,结果是地址值。内存和地址问题很重要,后面具体还会总结。
数组常见异常:
ArrayIndexOutOfBoundsException,数组越界异常,访问了不存在的索引,在循环遍历中需要注意
NullPointerException,空指针异常,数组不指向堆内存
接着是一些数组遍历、取最值、逆序等方法,都很基础,并且后面有专门的工具类提供方法。
二维数组有个小问题,初始化格式中 int[] y[];这样也是二位数组。
5、面向对象思想
强调对象,把复杂的问题简单化,从执行者变为指挥者
不需要考虑过程,也不需要考虑每个对象具体怎样实现,只需要访问对象,让其完成对应功能即可
6、类
(1)包括三个部分:
成员变量、构造方法、成员方法
(2)在成员方法中的变量为局部变量,他与成员变量的区别是
类中位置不同:但名字可以相同,调用时就近原则
内存中位置不同,结合生命周期:成员变量随着堆中对象消失,局部变量随着栈中方法调用完毕而消失
(3)形参的传递问题:
基本类型不影响实参,引用类型直接影响实参(但String类型此时又与基本类型用法相似,不影响实参,后面还有例子)
(4)匿名对象:
直接new一个对象来调用他的方法,而不用引用指向他。
当这个方法只用一次,可以用匿名对象实现,调用完毕就变成垃圾等待回收,节省空间。
可以结合安卓中按钮监听对象的使用来理解。
7、封装
引入private关键字,将类中的成员变量隐藏起来,不能直接通过.号来访问数据
提供对应的get和set方法,隐藏对象的属性和实现细节
提高代码的安全性和复用性
(1)this关键字:
我们给出对外公共的set和get方法时,为了方便使用和程序易读,
传入参数名和成员变量名一般是一致的
这时就需要使用this关键字来调用类中的成员变量,格式:this.成员变量 = 参数名;
this代表了此类的对象,虽然对外界成员变量是私有的,
但是类中还是可以直接以.号调用,从而实现这样的赋值
(2)构造方法:
每当我们new一个对象时,调用的都是他的构造方法。
但最开始我们其实并没有去写,因为系统默认会给一个无参的构造方法
(这里的原因是Object类中,只默认一个无参构造,
又因为所有类默认继承与此类,所以继承时只有一个无参构造方法)
格式:方法名与类名相同,没有返回值类型和返回值。
当我们自己去给出了一个构造方法,不管有没有参数,
系统将不再提供默认的无参构造,所以开发中尽量都是自己将构造方法写好。
(3)重载:
提到构造方法就要提到这个概念,构造方法可以有多个,
除了默认的无参构造,还可以传递参数,类似直接将set与get方法整合
写多个构造方法的过程就是重载,也有一定的规则:
每个构造方法的参数列表要不同,这里要理解的是,不光是个数与类型不同
同样两种参数,顺序不同也算不同
(4)static关键字:
静态的,被所有对象共享,随着类的加载而加载,用于定义一些所有对象共有的属性或方法
可以直接通过类名进行调用,所以很多常见工具类的方法都是静态修饰的
注意事项:
static中没有this关键字,因为静态内容是随着类的加载而存在,this只有对象创建时才存在
即静态的内容是比对象先存在的
静态方法无法访问非静态变量或方法,但非静态方法可以访问静态变量与方法
(5)代码块:
用{}括起来的部分,为代码块。分为局部代码块、构造代码块和静态代码块
局部位置代码块一般用于限定变量的生命周期
构造代码块是在类中成员位置的代码块,每次调用构造方法前都会执行,
用于将多个构造方法中共同的部分写在同一个代码块中,对对象进行初始化使用
静态代码块直接以static修饰代码块,只执行一次,类初始化时执行。
注意事项:
代码块的执行顺序为 静态-》构造-》构造方法,其中静态只执行一次,构造在每次调用构造方法都会执行
例子:看程序写结果
public class Test3 {
static {
System.out.println("static");
}
public static void main (String[] args) {
System.out.println("main");
Student s1 = new Student();
Student s2 = new Student();
}
}
class Student {
static {
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
public Student () {
System.out.println("构造方法");
}
}
这个程序输出为
static
main
静态代码块
构造代码块
构造方法
构造代码块
构造方法
分析:首先进入带有main方法的测试类,其中有一个静态代码块,需要先执行,输出static
然后进入main方法,顺序执行,先输出了main
接着才是创建引用,此时才回初始化学生类,初始化过程中有一个静态代码块,直接做了输出
再指向new的对象,此时对象才被创建,调用构造方法,并先执行构造代码块
当再次引用另一个变量来指向新的对象时,类在刚才已经被初始化,不再执行静态代码块,
直接输出构造代码块和构造方法中内容
8、继承
(1)定义:开发中经常遇到许多类会有共同的地方,例如猫狗,都是动物,此时就需要使用继承
定义一个父类,具有所有子类共有的属性和方法,用extends关键字实现继承
提高代码的复用性和维护性,使类之间产生了关系,是多态的前提
但也有弊端,开发的原则是低耦合高内聚,继承是一种类之间耦合性的增强
注意事项:
Java中只有单继承,不能多继承,可以多层继承
(但是可以通过接口的方式间接实现多继承)
继承过程中,子类只能继承非私有的成员
子类不能继承父类的构造方法,但可以使用super关键字访问父类构造方法
(2)构造方法细节:
子类可能会访问父类的属性,所以当子类初始化时,肯定先完成了父类的初始化,
子类所有构造方法都会默认的访问父类中的无参构造方法
子类的每个构造方法,虽然一般没有写出,但都默认是super();
一些情况中,父类可能没有无参构造,子类就无法实现默认构造,
此时就可以通过自己利用super调用父类的带参构造方法,
也可通过this调用本类中其他构造方法
需要注意,this和super都要出现在第一条语句
(3)继承中类和对象初始化问题:
结合之前的代码块执行顺序:静态代码块》构造代码块》构造方法
子父类之间有分层初始化,子类创建引用时,加载父类后加载子类,
但此时并没有执行其他构造部分,只有当指向了new的对象时,
才进行了对象的初始化,先进行父类构造,再进行子类构造
例:
//父类三种代码块
class Fu {
static {
System.out.println("父类静态块");
}
{
System.out.println("父类构造代码块");
}
public Fu () {
System.out.println("父类构造方法");
}
}
//子类继承和三种代码块
class Zi extends Fu {
static {
System.out.println("子类静态块");
}
{
System.out.println("子类构造代码块");
}
public Zi () {
System.out.println("子类构造方法");
}
}
这两个类定义后,如果通过测试类去Zi z = new Zi();执行完毕后的结果为:
父类静态块
子类静态块
父类构造代码块
父类构造方法
子类构造代码块
子类构造方法
分析:执行这个语句先是左边创建一个引用,进行了类的初始化,从父到子,但并没有构造对象,只执行了两个类中的静态代码块,当new了一个对象之后,才从父类构造开始对对象进行初始化。
另一个例子:
class X {
Y b = new Y();
X () {
System.out.print("X");
}
}
class Y {
Y () {
System.out.print("Y");
}
}
public class Z extends X{
Y y = new Y();
Z () {
System.out.print("Z");
}
public static void main (String[] args) {
new Z();
}
}
这个程序输出结果是:
YXYZ
分析:从main方法进入后直接new子类对象,此时是先进行父类对象初始化,所以在父类中执行了构造输出YX
接着是回到子类对象初始化,输出了YZ。是对分层初始化的理解
刚开始可能会想的太多,认为像上面例子一样,进入父类初始化后先回到子类初始化再构造父类对象(可能想成YYXZ),但这里其实可以理解为
访问Y的部分就是构造代码块,虽然在构造外层,也是随着构造方法执行,而且这个例子是直接new的对象,并不像上面例子中先创建引用进行父类初始化,这里是直接执行到父类对象初始化完毕才进入子类进行对象初始化。
(4)方法重写:
子类中可以定义与父类中同名的方法,实现不同的功能,这个过程就是实现了重写
因为同名的东西,都是采用就近原则执行,所以才有了重写,但重写除了写新的功能,也可以通过super调用父类方法
这样既保留了父类中通用的属性方法,又添加了新的内容
注意事项:
父类私有方法无法被重写,因为本身无法继承
子类重写父类方法时,权限不能降低,开发中最好一致。
重写(override)与重载(overload):
前者是子类中与父类同名方法声明的现象
后者是同类中,对同名方法进行不同参数列表声明的现象
this与super:
this代表当前类的对象引用,super代表父类存储空间的标识
应用场景有访问成员变量、构造方法、成员方法。
final关键字:
除了不让子类继承的private方法,还存在一种只是不想让子类重写但允许其使用的情况
这时可以用final来修饰
除了修饰方法,修饰变量时,只能赋值一次,但这里需要理解的是
修饰基本数据类型,相当于将变量变成了常量,值是无法改变的
修饰引用类型时,只是地址值不能改变,值是可以改变的
比如有一个学生类,一个属性是name,那么final引用一个学生对象时,name是可以改变的,但这个引用不能指向另一个new的学生对象。
另外,final也可以修饰类,一般用于最底层的类,不再能继承。
9、多态:
(1)定义:同一对象在不同时刻体现不同的状态,比如可以引用一个动物变量指向一个猫或狗的对象。
多态的前提是继承,并且有方法的重写(虽然不是硬性要求,但如果没有,多态就没有了存在的意义)
提高了代码的扩展性,可以适应各种子类共性,也提高了代码的维护性(这里是因为继承的特点)
但需要使用子类特有功能时,可以向下转型为子类,只有确定能够强制转换时才能这样做
(2)特点:各成员的编译运行
成员变量:编译父类,运行父类
成员方法:编译父类,但运行时用的是子类,因为进行了方法的重写
静态方法:编译运行都是父类的,所以子父类同名的静态方法并不是真正意义的重写
(3)异常:
强制转换中转换了不能转的对象,编译时不会报错,因为符合规则,但运行时会出现异常
ClassCastException类转换异常,一般出现在向下转型
(4)例子:看程序写结果
public class Test3 {
public static void main (String[] args) {
A a = new B();
a.show();
B b = new C();
b.show();
}
}
class A {
public void show () {
show2();
}
public void show2 () {
System.out.println("我");
}
}
class B extends A {
public void show2() {
System.out.println("爱");
}
}
class C extends B {
public void show () {
super.show();
}
public void show2 () {
System.out.println("你");
}
}
输出结果:爱你
分析:进入main方法,a.show();调用show2();而show2()在子类B中重写,输出为B中的show2();“爱”
接着b.show();运行看C中重写的show方法,而C中正好返回父类B中的show,但发现b中没有show,其实隐含了直接继承A的show方法
所以运行show2方法,而C中对show2进行了重写,输出的是“你”。
10、抽象:
(1)定义:父类中,许多成员不应该是具体的,而应该是给出拥有的特性,大多数让子类去重写,这时就要引入抽象,用关键字abstract定义抽象类
(2)注意事项:
抽象类中不一定所有成员都要抽象,但反过来只要有一个抽象成员,那么这个类一定要是抽象的
抽象方法不能有方法体,这里的理解是括号后直接接分号;,用大括号括起部分即使里面没有内容,也算一个空方法体。
抽象类继承是,子类必须重写所有抽象方法才能是一个具体的类,如果不重写就需要也定义为抽象类
但抽象类不能实例化,这样也就没有了意义
抽象类想要进行实例化,是以多态的方式通过具体子类进行实现。
(3)成员特点:
成员变量:可以是常量也可以是变量
构造方法:存在,虽然不能构造对象,但可以访问父类数据时需要
成员方法:可以抽象也可以非抽象;抽象必须重写,非抽象直接继承,提高代码复用性
这里可能会被问到一个问题:如果一个类是抽象类,但是没有抽象方法,意义何在?
答:禁止创建父类对象,让其只能通过子类多态实现方法调用。
(4)关键字共存问题:
private:冲突,因为private不允许重写,而abstract要求必须重写。
final:同样冲突,final是最终的,只能赋值一次,不能继承和重写,而抽象要求必须重写。
static:没意义,抽象方法是通过子类重写实现一些功能,而静态是直接以类名调用方法,这样调出来的是没有方法体的功能。
11、接口:
(1)虽然子类中可以继承父类,并且增加一些功能,但有些特性并不是所有子类都拥有,而又不方便在父类中定义。这时就引入了接口,关键字interface。以implements实现
(2)特点:
接口是抽象的,不能实例化,但也是能用子类以多态实现实例化。
接口的子类,可以是抽象类但没有意义,具体类要重写所有方法
成员变量:默认是常量,修饰符public static final,即使自己写其中一个也默认是三个
构造方法:接口没有构造方法,子类中构造方法默认继承与Object类
成员方法:只能是抽象方法,默认修饰符public abstract
(3)继承问题:
类与类:单继承,多层继承
类与接口:实现关系,可以多实现,间接实现多继承
即一个类可以继承一个父类的同时实现多个接口
接口与接口:这里可以多继承!
因为多继承类时,多个父类如果出现同名变量方法,则会错乱,不允许多继承
而接口里面都是抽象方法,变量也都默认成为常量,静态域只有自己使用。
(4)接口与抽象类的区别:
抽象类:成员变量可以是变量和常量,有构造方法,成员方法抽象与否都可以。
接口:只有常量,没有构造方法,成员方法只能抽象。
继承关系上结合(3)即可
设计理念的不同:
抽象类实现继承时,属于“is-a”关系,体现了共性
接口与类是实现功能,属于“like-a”关系,体现扩展性
(5)作为形参传递问题:
类:传入的是该类对象
抽象类:传入其子类对象
接口:传入实现该接口的类的对象
作为返回值时同理。
12、修饰符的综合总结:
(1)权限修饰符:
public,公用的,在不同包、不相关类中皆可访问
protect,保护的,同包内、相关子类中访问
默认(不写或者friendly),包内访问
private,私有的,只能在本类访问和使用
(2)状态修饰符:
static:静态的,修饰方法时可以直接用类名进行调用
final:最终的,只能赋值一次或给一次地址值
(3)抽象修饰符:abstract
(4)应用:
类:默认public,可以抽象abstract修饰,也可以在底层类用final修饰
成员变量:除了abstract,其他修饰符都可使用,private较多
构造方法:只能用权限修饰符,不能用状态修饰符和抽象修饰符
成员方法:根据功能不同,所有修饰符都可以使用
13、内部类:
(1)定义:定义在类中的类
内部类可以访问外部类的成员,包括私有成员
外部类访问内部类,需要创建对象
(2)分类:
成员内部类:在类的成员位置,一般设置为private后,给出其他公共的成员方法进行访问;可以用静态修饰,但此时只能访问静态成员
局部内部类:在类中的方法内。
例:如何输出30,20,10
public class Test2 {
public static void main (String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(?);
System.out.println(??);
System.out.println(???);
}
}
}
?:num;??:this.num;???:Outer.this.num;(也可以是new Outer().num)
第一个输出就近原则直接取方法内的num,第二个输出内部类的num直接用this关键字,第三个有可能会认为用super,但内部类与外部类是没有继承关系的,所以直接用Outer.this表示外部类对象再调用num。
(3)匿名内部类:(结合安卓理解)
前提:存在一个类或者接口
格式:new 类或接口名(){重写方法;};
理解:说是一个类,但实际上是一个继承了该类或接口的子类匿名对象,重写了方法。不需要单独写一个类去继承,在只需要调用少次方法时,这样更方便。体现的是多态的思想
例子:补全程序,输出“helloworld”
public class Test5 {
public static void main (String[] args) {
OuterClass.method().show();
}
}
interface InterFace {
void show ();
}
class OuterClass {
//补全此处
}
分析:进入main方法,OuterClass直接以.号访问method方法,可以看出此方法是静态修饰
接着OuterClass.method()整体可以用点号访问show方法,说明返回的是一个InterFace对象,只有对象才能访问方法
需要注意的是接口中方法只用void修饰,但其实默认还是public abstract void,重写时不能忘了public
答案是:
public static InterFace method () {
return new InterFace() {
public void show () {
System.out.println("helloworld");
}
};
}
返回接口对象,声明中要加入InterFace,方法要返回其对象,但接口不能实例化,实质上返回的是子类对象,并直接重写了show方法输出结果,也是多态思想。安卓中按钮监听器都是这种写法。
14、eclipse常用快捷键:
ctrl + shift + o;快速导包
ctrl + shift + f;格式化代码
alt + /;输入提示,这里可以在设置中将所有字母都设置成提示关键字,默认只有点号
alt + shift + s;可以实现类中各种方法的重写,还要get与set方法的自动生成
15、Object类:
所有类的父类,所有类都直接或间接的继承这个类,只有一个无参构造方法
(1)主要方法:
hashCode();返回对象的hash码
getClass();返回对象的运行时类,可以用一个Class类引用接收,再调用getName方法获取类名(包括包名)
toString();返回该对象的字符串表示,默认下等于this.getClass().getName() + @ +Integer.toHexString(hashCode())
但这样似乎没什么用,所以一般都会在类中重写方法,输出成员变量值的组成,eclipse中也有自动重写功能
finalize();用于垃圾回收
clone();返回一个此对象副本,一般会进行重写
(2)重点说一下equals()方法:
经久不衰的面试问题,与==号的区别。
==号:在比较基本类型时,比较值是否相等;比较引用类型时,比较地址值是否相同
所以当比较两个成员变量赋值相同的对象时,还是返回false,因为地址不同
equals():查看源码,其中判断是this == obj;所以默认情况下就是==比较地址值,但意义不大
一般进行重写,比较变量的值是否相等,重写后再次比较两个变量相同对象时,返回true。
16、Scanner类:
(1)键盘录入,使用格式Scanner sc = new Scanner(System.in);需要导包
这里System类下有一个静态字段:public static final InputStream in;返回一个输入流。
所以使用scanner时用的构造方法是Scanner(InputStream source);
(2)常用方法:
hasNextXxx,判断是否有Xxx类型字段
nextXxx,接收Xxx类型字段,常用接收int与String
注意事项:
获取两个数据时,获取两个数字、获取两个字符串、先一个字符串再一个数字,都不会出现问题
但是如果先获取一个数字再获取一个字符串,那个字符串是接收不到的,因为我们需要用回车确认一个输入完毕
而输入完数字回车,获取字符串时直接将回车符作为了字符串获取
解决方案:重新创建一个scanner对象接收第二个字符串。或者把所有数据通过字符串获取,需要使用特定格式再转换。
17、String类:
(1)字符串就是多个字符组成的一串数据,是lang包下的类,不需要导包。
本质是字符串常量,不能更改,但这个不能更改的理解,要通过以下例子:
String s = "hello";
s += "world";
System.out.println(s);
这里输出仍然是helloworld,但与不能更改并不矛盾,这个过程中“hello”这个字符串在方法区被创建,并赋给了s
之后“world”被创建,并且与“hello”拼接生成了“helloworld”再赋值给了s,这个过程实际产生了三个字符串
不能改变只是说第一个“hello”存在于方法区并没有被改变,而s只是引用,指向不同地址则输出不同结果
(2)结合==与equals问题:
例子:
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);// false
System.out.println(s1.equals(s2));// true
new了两个对象,地址不同,内容相同,问题不大。
String s3 = new String("hello");
String s4 = "hello";
System.out.println(s3 == s4);// false
System.out.println(s3.equals(s4));// true
对象在堆中,s3指向堆内存中的对象地址,s4直接指向方法区中常量池的“hello”字符串地址,问题不大。
String s5 = "hello";
String s6 = "hello";
System.out.println(s5 == s6);// true
System.out.println(s5.equals(s6));// true
指向方法区内常量池中同一个字符串地址,内容也相同,问题不大。
String s7 = "hello";
String s8 = "world";
String s9 = "helloworld";
System.out.println(s9 == s7 + s8);//false
System.out.println(s9.equals(s7 + s8));//true
这里s7 + s8,属于变量相加,先开辟了一块空间,虽然空间指向“helloworld”字符串,但s9此时指向的是新开辟的空间地址
System.out.println(s9 == "hello" + "world");//***true
System.out.println(s9.equals("hello" + "world"));//true
这里需要注意,此时没有变量,相当于后面两个字符串直接拼接,拼接后发现方法区常量池存在一个“helloworld”字符串
所以地址与直接指向该字符串的s9相同,返回true。以上每个equals都返回true是因为String类中重写了方法,比较内容。
(3)一些方法:
判断:
equalsIgnoreCase(String str)忽视大小写的比较内容是否相同
contains,判断是否包括连续的小字符串
startWith判断是否以指定字符开头
isEmpty();判断是否为空
这里需要注意,是判断内容是否为空,如果String s = null;则会产生空指针异常
因为null说明连对象都没有,根本无法调用方法。空串指的是“”。
获取:
length();返回字符串长度
charAt(index);返回指定索引的字符,反过来indexOf(char)可以查询指定字符出现的第一个索引
substring(start,end)截取字符串,包括start,不包括end。
转换:
getBytes返回一个字节数组
toCharArray转换为字符数组
valueOf(类型很多)将其他类型数组转换为字符串
其他:
replace替换字符
trim去除两边空格
compareTo,按字典顺序比较,返回第一个不同字母的ascii码差值,如果都相同,但长度不同,返回长度相减值
18、StringBuffer类:
(1)String类字符串是固定的,如果每次都进行字符串拼接,会占用太多空间
StringBuffer就解决了这个问题,是一个缓冲区,空间可以变化,做拼接会更节省资源
(2)构造方法:
StringBuffer()无参构造
StringBuffer(int capacity);指定容量,默认为16
StringBuffer(String str);指定内容
(3)成员方法:
length();实际字符串的长度
capacity();整个缓冲区容量
如果指定内容构造,这里容量是该字符串的length+16.
append();将任意类型添加到缓冲区,类似一个水杯,每次返回的是对象本身,例:
StringBuffer sb = new StringBuffer();
StringBuffer sb2 = sb.append("hello");//没有重新开空间
System.out.println(sb);//hello
System.out.println(sb2);//hello
System.out.println(sb == sb2);//true
insert();插入
deleteCharAt()删除指定位置字符,直接delete()传入start和end,可以删除范围内字符
reverse();反转功能
以上几个操作功能基本返回对象本身,操作完对象本身变化了
substring()返回的是字符串,截取后的字符串给一个String 引用,但自身不变
(4)与String的相互转换:
String转换为StringBuffer
构造方法中可以直接传入String
或者创建对象后,用append方法将字符串拼进去
StringBuffer转换为String
利用substring方法截取返回String
或者String构造中传入StringBuffer对象也是可以的,这个构造方法也存在
还可以用toString方法实现
(5)String、StringBuffer与StringBuilder区别:
String与后两个的区别在于,内容不可变,只能做字符串拼接,浪费空间
后两者都是可变缓冲区存放字符串
StringBuffer与StringBuilder使用方法几乎相同,但引入了一个概念
前者是线程安全的,同步的,效率较低
后者可看做前者的简易版,但不同步,不是线程安全,效率较高
所以单线程编程中,大多使用StringBuilder。
另外,后两者与数组的区别:
数组只能存放同一种类型的数据
StringBuffer可以存放不同类型,最后是一个字符串
19、排序与查找
回顾了简单的冒泡排序,选择排序和二分查找
冒泡排序:数组中两两对比,大的放在后面,比较结束后,最大元素在数组结尾
每轮比较结束后,只需比较到上一次索引-1的位置上
public static void sortArray(int[] arr) {
int tmp;
// 外层控制排序次数
for (int x = 0; x < arr.length - 1; x++) {
// 注意数组越界,内层两两比较
for (int i = 0; i < arr.length - 1 - x; i++) {
if (arr[i] > arr[i + 1]) {
tmp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = tmp;
}
}
}
//不需要返回值
}
选择排序:从第一个元素开始,与后面的每个元素比较,小的就交换到此位置,比较结束,最小元素在数组开头
每轮比较结束后,只需从下一个元素跟后面比较即可
public static void sortSelect(int[] arr) {
int tmp;
// 外层拿每个索引
for (int i = 0; i < arr.length - 1; i++) {
// 内层与每个比较
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[i]) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
二分查找:对于有序数组,可以使用二分查找,类似于猜数字游戏中,我们每次都会从范围中间值猜起。
public static int binarySearch(int[] arr, int value) {
int min = 0;//最小索引
int max = arr.length - 1;//最大索引
int mid = (min + max) / 2;//中间索引
while (arr[mid] != value) {//没找到就持续比较
if (arr[mid] > value) {
max = mid - 1;//猜数字大了就在前半部分再猜
} else if (arr[mid] < value) {
min = mid + 1;//猜小了就在后半部分再猜
}
if (min > max) {
return -1;//最小索引比最大索引还大,说明找不到了。直接跳出
}
mid = (min + max) / 2;//重新计算中间索引值
}
return mid;//找到则返回当前索引值
}
20、Arrays类:
自己进行排序和查找,十分麻烦,所以可以使用自带的工具类Arrays
方法:(以类名直接调用)
toString数组转为字符串(数组直接.toString()返回的是地址值,Arrays.toString进行了重写)
sort();排序,这里源码使用的是快速排序
binarySearch();查看源码发现比上面设计要完善的多,并且优化的很好,考虑也更全面
比如以后自己开发,大多数方法中也要先进行null判断,防止空指针异常。
21、包装类:
(1)基本类型有时我们也想要去调用方法实现一些功能,而他们本身不能做到
所以Java对他们每个类型都提供了包装类,除了方便调用方法,还可以实现与字符串之间的转换
(2)以Integer为例
Integer(int value)将int转为包装类
Integer(String str)将字符串转成包装类,但字符串中必须是数字字符组成
int到String的转换
除了之前提的valueOf方法,可以以Integer为中介,调用toString进行转换
String s = Integer.toString(int i);
反过来,提供了一个方法,将字符串转为int型
Integer.parseInt(s);
(3)进制转换
toBinaryString()/toOctalString()/toHexString()分别可以转2、8、16进制
toString(int i , int radix)向任意进制转换,范围2-36进制
(4)自动拆装箱
Jdk5以后的新特性
原本需要一个Integer对象需要Integer i = new Integer(100);
而现在可以直接Integer i = 100;这里就是自动装箱
包括i += 100;这样的操作,原本是先进行拆箱,转为int型,计算完毕后再进行装箱。
本来的写法应该是 i = Integer.valueOf(i.intValue() + 100);
同样 Integer i = null;这样的操作是不行的,null没有对象,不能使用自动拆装箱中的方法。
(5)依旧是==与equals问题
Integer i = new Integer(128);
Integer ii = new Integer(128);
System.out.println(i == ii);//false,对象不同
System.out.println(i.equals(ii));//true
创建了不同的对象,地址不同,内容相同,问题不大。
Integer i2 = 127;
Integer ii2 = 127;
System.out.println(i2 == ii2);//true,在常量池中,地址相同
System.out.println(i2.equals(ii2));//true
在byte范围内,直接去常量池中值,两个地址相同,内容相同,问题不大。
Integer i3 = 128;
Integer ii3 = 128;
System.out.println(i3 == ii3);//false,不在常量池中,创建了新对象
System.out.println(i3.equals(ii3));//true
超出了byte范围(-128~127),会自动创建新的对象,地址不同,内容相同,需要注意。
其他类型的包装类,方法原理都类似,可以读源码得知。
22、正则表达式(Linux中也常用)
(1)定义:符合一定规则的字符串
String regex = “规则”;
(2)规则:
符号类:
x 表示字符本身
\\ 表示\单个反斜杠符
\n 换行
\r 回车
字符类:
[abc] 表示a/b/c单个字符
[^abc] 表示除了abc以外的字符
[a-zA-Z] 表示所有字母
[0-9] 表示所有数字
预定义:
. 点号表示任意字符,如果需要的就是点号本身,用反斜杠 \.
\d 任意数字的另一种写法,大写表示除了数字,相当于[^0-9]
\w 任意字母和数字的写法[a-zA-Z_0-9],大写W同上。
边界:
^ 表示一行的开头
$ 表示一行的结尾
\b 表示单词边界
数量:
X? 表示x出现1或0次
X* 表示x出现0次或多次(包括一次)
X+ 表示x出现1次以上
X[n] 表示x出现n次
X[n,] 至少n次
X[m,n] 出现m到n次
(3)应用:
String类中有一些方法,传入规则后可以实现相应功能
matches(String regex)判断是否满足指定规则,返回boolean类型
split(regex)以特定规则进行拆分,返回一个字符串数组
replaceAll(regex,replacement)按特定规则,全部替换
(4)模式和匹配器:
Api(application programing interface)中给了一种使用正则表达式的标准格式
* Pattern p = Pattern.compllie(regex);
* Matcher m = p.matcher(s);
之后调用find()/group()等方法
23、Math工具类:
(1)一些常用的量与方法
public static final double PI
abs();取绝对值
cell();向上取整
floor();向下取整
max();min();最大最小
pow(a,b)返回a^b
random();常用的随机数
round();四舍五入
sqrt();取正平方根
(2)关于random方法,有一个小问题:取得任意范围的随机数
我们知道得到0~99之间随机数,就是Math.random() * 100;
其实同理的:
public static int getRandom(int a, int b) {
// int num = (int) (Math.random() * b) + a;这样不行
int num = (int) (Math.random() * (b - a + 1)) + a;
return num;
}
我们可能容易忘记在乘以100时,我们获取的是从0开始的数,要记得减掉范围最小值。
(3)Random类
其实获取随机数,有一个专门的工具类,更加方便获取任意范围
构造方法除了空构造,还有一个传入种子的构造Random(long seed);
成员方法:
nextInt();获取int范围内的随机数
nextInt(int n);获取[0,n)之间的随机数
至于构造方法中的种子,我去尝试了一下,大概理解为
给定了种子的对象,在获取随机数时,只要范围不变,随机数也不变
但如果范围变了,会取到另一个随机数,多次执行,还是不变
种子改变,随机数也改变
总结一下就是,同一个种子在同范围内获取的随机数不变。
24、System类
(1)final修饰的类,不能实例化
(2)一些方法:
gc();运行垃圾回器,默认调用Object中的finalize()方法,一般来说,除了大量回收对象之外,让虚拟机自己决定何时回收即可
exit(int status);终止当前运行的虚拟机,传入非0则表示异常终止
currentTimeMillis();返回毫秒为单位的当前时间,默认从1970-1-1开始
arraycopy(Object scr,int srcPos,Object dest,int destPos,int length);
从指定数组的指定位置开始,复制到目标数组,也从指定位置开始,length代表复制个数,改变的只有第二个数组。
25、Big型类:
当Integer范围都不够用时,可以用BigInteger,基本的方法有加减乘除等操作
小数运算时,float和double容易丢失精度,产生类似0.99999+这样的数值
在金融项目中,这样的误差将会出现很大问题
所以Java提供了BigDecimal类,有一个构造方法可以传入字符串,进行数字运算,则不会产生误差
26、Date与Calendar类
(1)Date,表示时间,精确到毫秒
无参构造方法创建对象,输出是当前时间
传入毫秒数,则是将时间设置为从1970.1.1开始该毫秒数的时间。
(需要注意的是我们中国是东八区,有八个小时时差,默认时间从1970.1.1的8:00开始)
一些方法:
getTime();获取时间
setTime();设置时间
格式化:
提供了一个抽象类DateFormat实现日期与字符串的转换
由于是抽象类,需要用子类初始化对象调用方法
例子:
public static void main(String[] args) throws ParseException {
Date d = new Date();
//Date 到 String 格式化
SimpleDateFormat sdf = new SimpleDateFormat();//可以自定义格式
String s = sdf.format(d);
System.out.println(s);
//String 到 Date 解析
String ds = "2018-03-18 20:08:50";
//解析时必须将格式写出,与给定字符串匹配
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date df = sdf2.parse(ds);//抛出异常
System.out.println(df);
}
解析格式必须相同,并且需要抛出异常才能使用。
(2)Calendar,日历,是一个抽象类,获取当前时间,Calendar c = Calendar.getInstance();右边实际上是子类对象。
一些方法:
get(int field)返回需要的日历字段的值,字段都是静态变量,如Calendar.YEAR。
add(field,amount)对指定的日历字段值进行加减
set(year,month,date)方便的直接设置日期
一个小题:获取任意一年的2月有多少天
正常去做,可能要考虑闰年等因素。但有了Calendar,则只需要先得到某年的3月1日,再对日期进行-1操作,获取日期字段即可
// 键盘录入年份
Scanner sc = new Scanner(System.in);
System.out.println("year:");
int year = sc.nextInt();//获取想查询的年
Calendar c = Calendar.getInstance();//随意创建一个日历对象
//设置日期为3月1日,需要注意月份是从0开始到11计数
c.set(year, 2, 1);
c.add(Calendar.DATE, -1);//对查询年3月1日进行-1操作,即得到2月最后一天
System.out.println(c.get(Calendar.DATE));//获取日期字段输出
这些大概是基础中的基础了,做个笔记。
后面就是集合框架,一直是我比较头疼的地方;还有IO流、多线程,反射等,都很重要
GUI可能就不会应用太多了,毕竟现在Java的窗口应用太少,都是服务器端开发和算法之类。
继续努力,过完基本知识,看web,学框架。
毕设是安卓,还得学学基础,完成简单功能。
以后走什么方向呢,总之把Java学好就行了吧~