Object--所有类的顶级父类,Object是java中唯一没有父类的类。方法:
- clone():克隆并产生一个新对象,新对象的地址和原来的对象不一样但是属性值一样。这个对象要想被克隆,所对应的类必须实现一个接口----Cloneable这个接口中没有任何的方法和属性,只是用来标识这个类产生的对象可以被克隆。
- equals(Object o):判断两个对象是否相等。
- toString():对象的字符串形式。
String
1)代表字符串的类,是一个最终类。
2)字符串不在栈中,也不在堆中,而是放在方法区的常量池中。
- equals(Object o):判断两个对象的内容是否相等。
- toString():
- 注意:
①字符串一旦被初始化,就不可以被改变,存放在方法区中的常量池中。
②对象的值(在常量池中的对象即内容)在创建之后不可更改,但是对象指向的地址可变。
③由于字符串是一个常量,所以是共享的。
String、StringBuilder、StringBuffer比较:
1.运行速度:StringBuilder > StringBuffer > String
String是字符串常量,StringBuffer和StringBuilder都是字符串变量。String对象创建好之后不可更改,StringBuilder和StringBuffer可以改变。
例如:
String str = "abc";
String str2 = "hel" + "lo";//在编译期间会被JVM优化为hello
System.out.println(str);
str = str + "de";
System.out.println(str);
运行结果:abc
abcde
解释:JVM首先创建对象str赋值abc,然后又创建了对象str赋值abcde,原来的str会被GC回收掉。实际上str并没有被更改,Java对String对象的操作实际上是一个不断创建新对象然后将旧对象回收的过程,所以执行速度慢。
2.线程安全:StringBuilder是线程不安全,StringBuffer是线程安全。
原因:StringBuffer中很多方法都带有synchronized关键字,保证线程安全,比如append和insert。
3.总结
- String适合于少量字符串操作的情况。
- StringBuilder适合于单线程下载字符串缓冲区进行大量操作的过程。
- StringBuffer适合于多线程下载字符串缓冲区进行大量操作的过程。
- 对String进行操作,它不会改变原来的值,而对于StringBuilder和StringBuffer会改变字符串的值。
- 单线程操作,使用 StringBuilder 效率高;多线程操作,使用 StringBuffer 安全。
- 如果频繁地对字符串的内容做增删改操作,首选StringBuffer和StringBuilder。
面试题
1.下面代码的输出结果为?
String a = "hello2";
String b = "hello" + 2;
System.out.println((a == b));
输出结果为:true。原因很简单,"hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。
2.下面代码的输出结果为?
String a = "hello2";
String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:false。由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。
3.下面代码的输出结果为?
String a = "hello2";
final String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2;
4.下面代码的输出结果为?
public class Main {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));
}
public static String getHello() {
return "hello";
}
}
输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。
5.下面代码的输出结果为?
public class Main {
public static void main(String[] args) {
String a = "hello";
String b = new String("hello");
String c = new String("hello");
String d = b.intern();
System.out.println(a==b);
System.out.println(b==c);
System.out.println(b==d);
System.out.println(a==d);
}
}
输出结果为false、false、false、true。在String类中,intern方法是一个本地方法,在JAVA SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。因此,a和d指向的是同一个对象。
6.String str = new String("abc")创建了多少个对象?
很显然,new只调用了一次,也就是说只创建了一个对象。
而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都说是2个对象呢,这里面要澄清一个概念 该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。
因此,这个问题如果换成 String str = new String("abc")涉及到几个String对象?合理的解释是2个。
在面试的时候如果遇到这个问题,可以向面试官询问清楚”是这段代码执行过程中创建了多少个对象还是涉及到多少个对象“再根据具体的来进行回答。
7.下面这段代码1)和2)的区别是什么?
public class Main {
public static void main(String[] args) {
String str1 = "I";
//str1 += "love"+"java"; 1)
str1 = str1+"love"+"java"; //2)
}
}
1)的效率比2)的效率要高,1)中的"love"+"java"在编译期间会被优化成"lovejava",而2)中的不会被优化。
在1)中只进行了一次append操作,而在2)中进行了两次append操作。
String a="";
int b=0xb;
String c=a+b;
String a="a";
String b="b";
String c= a.concat(b);
理论上,此时拼接效率应该最高,因为已经假定两个量都为字符串,做底层优化不需要额外判断或转换,而其他方式无论如何优化,都要先走到这一步。
字符串拼接的几种方式
1.+号拼接:当左右两个量其中有一个为String类型时,用+方式可将两个量转成字符串并拼接。
String a="";
int b=0xb;
String c=a+b;
2.concat方式:当两个量都为String类型且值不为null时,可以用concat方式。
String a="a";
String b="b";
String c= a.concat(b);
理论上,此时拼接效率应该最高,因为已经假定两个量都为字符串,做底层优化不需要额外判断或转换,而其他方式无论如何优化,都要先走到这一步。
3.append方式:当需要拼接至少三个量的时候,可以考虑使用StringBuffer的append()以避免临时字符串的产生。
正则
作为模式或者规则进行匹配的。
数学类
- Math:最终类。针对基本类型提供基本的数学运算。
- BigDecimal:用于精确存储和运算小数的类。要求参数以字符串形式传递
- BigInteger:能存储和计算任意大小的整数的类。
-
当需要精确运算的时候,需要将数据以字符串形式传入,因为字符串在底层是以字符数组存储的,例如:BigDecimal bd2=new BigDecimal("2.98");
包装类
- 为什么引入包装类:为了便捷的操作数据,java针对每种基本类型提供了对应的类形式。
- byte-Byte short-Short int-Integer long-Long
float-Float double-Double boolean-Boolean char-Character
- 只有int和char发生了改变,其他只是首字母大写。
- 前七个包装类都提供了将对应形式以及将字符串转换为对应对象的构造方法;其中对于Boolean类型,会在底层做一个判断return s==null?false:s.equals(“true”);,即只要字符串不是”true”就返回”false”.
- 但是Character不是数值类的子类,而是Object的子类,所以Charcter类只提供了将字符转换为对应对象的构造方法,并没有提供将字符串转换为字符的方法。
- 自动封箱和自动拆箱:从JDK1.5开始
- 封箱和拆箱:从JDK1.0开始
日期类
Date/Calendar---重点掌握日期格式转化。
从JDK1.8开始,对时间体系进行了新的划分,将日期和时间进行分离,并且详细区分,从而形成了一个新的包---时间包 java.time。
异常
java中一套用于反馈和处理问题的机制,解决方式:抛出或者try-catch。
1)抛出:
- throw:由程序员根据指定的业务条件 throw new 异常类型()
- throws: 放在方法声明的后面,可以写很多的异常类型,用逗号隔开;throws必须写在方法声明的后面,表明此方法体中,可能会抛出的异常类型,谁调用此方法,谁去处理try--catch。
2)try-catch
try-catch:
try{
}catch( ){
}如果没有异常发生,就跳过catch语句块
Throwable--是Java中异常的顶级父类,有两个子类Error和Exception
- Error---表示严重的不应该试图捕获的错误---一旦出现不能处理。
- Exception---表示程序中出现的一些特殊情况。
- getMessage();---获取异常信息。
- printStackTrace();---打印栈轨迹。
- 运行时异常/未检查异常---在编译的时候不报错但是运行的时候默认抛出异常---往往在语法上无错--可以不处理,可以处理---RuntimeException
- 主函数中的异常最终会抛到JVM中,java虚拟机接收异常,会默认打印异常的栈轨迹。当前面代码一旦抛出异常,后续代码不再执行。在try里面发生异常语句的后边不会被执行;如果try块中的语句被后面的catch块捕获并处理了,那么try-catch后面的语句依然执行,如果不被捕获,后面的就不能执行。
注意:
- 如果方法中只抛出了父类异常,那么必须捕获父类异常,并且注意顺序,从小到大依次捕获,否则小异常放在后面永远执行不到;
- 异常不影响方法的重载;
- 异常影响方法的重写----因为对于编译时异常而言,必须抛出才能捕获,子类方法抛出的异常不能超出父类方法异常的范围,即子类不能抛出比父类更多的异常,更多是指范围不能更大;但是对于运行时异常而言,可以抛出可以不抛出。
- 无论执行成功与否,也不管try代码块中是否有break、continue、return、throw语句,finally代码块都会执行一次。
- try-catch-finally允许嵌套。
- 在出现和未出现异常的情况下都要执行的代码可以放到finally语句块中。加入了finally语句块后有以下三种情况:
集合
- 存储一组数据,数据的个数不定---集合是一个存储一组数据的容器,这个容器的大小不固定。
- 容器:变量(存储一个数据)、数组(存储定长数据)、集合(存储不定长数据)
- 集合就是一堆数据的集合体,集合框架就是数据结构。
- 使用集合的目的:
- 集合的大小不会固定,可以随时增加。
- 可以操作集合中的数据
- Collections-----操作集合的一个工具类。
- Collection<E>--是集合的顶级接口。E是一个泛型
- List---列表,是一个接口,接口中定义的规范:有序(元素存入集合的顺序和取出的顺序一致)的Collection,保证元素的存入顺序,元素都有索引。允许存储重复的元素。
- ArrayList—List的一个实现类
- 基于数组的,默认初始容量为10。10 -> 15 -> 22.,内存空间是连续的。
- 最常用的List接口实现类,底层使用变长的动态数组实现,ArrayList初始容量是10,当元素数量大于初始容量时需要扩容,每次扩容之后产生一个新的数组,新的长度=旧长度+旧长度/2。
- 因为每个元素都有固定的位置索引,所以根据索引查询元素速度非常快;如果插入元素时,全部元素需要后移,性能比较差。
- 没有做并发访问控制,是一个非线程安全的集合,允许重复元素或null。
练习题
1.
解释
- 执行顺序:静态代码块--->动态代码块--->构造函数
- 静态代码块只在第一次new的时候执行一次,之后不再执行;动态代码块在每次new的时候都执行一次。
- 没有继承的情况下:
- 静态代码块和静态变量在加载类时执行,只执行一次,按照出现顺序先后执行。
- 动态代码块在每次实例化对象时执行,在构造函数之前执行,多个动态代码块按照出现顺序执行。
- 有继承的情况下:
- 执行父类静态代码块和静态变量。执行子类的静态代码块和静态变量。
- 执行父类的动态代码块,执行父类的构造;
- 执行子类的动态代码块,执行子类的构造;
- 如果父类构造中用的方法被子类重写过,那么在构造子类对象时调用被重写过的方法。
输出结果为
原因:
- 先初始化k;
- 在初始化静态变量t1的时候,会重新加载类test02,由于静态只加载一次,所以会执行j的初始化,打印出1:j i=0 n=0;
- 然后执行普通代码块,打印出2:构造快 i=1 n=1;
- 然后回来完成t1的初始化,打印出3:t1 i=2 n=2
- 继续往下运行初始化t2,同样再加载类test02,同理执行j的初始化,打印出4:j i=3 n=3;
- 然后执行普通代码块,打印出5:构造快 i=4 n=4;
- 然后回来完成t2的初始化,打印出6:t2 i=5 n=5;
- 然后初始化i,调用print方法,打印出7:i i=6 n=6;
- 然后初始化n;
- 然后初始化静态代码块,调用print方法,打印出8:静态块 i=7 n=99;
- 然后初始化普通变量j,打印出9:j i=8 n=100;
- 然后初始化代码块,调用print方法,打印出10:构造快 i=9 n=101;
- 然后执行构造,打印出11:init i=10 n=102;