Java基础
String、StringBuffer、StringBuilder
- 使用char[]保存字符。String中使用了final修饰字符数组,使得引用不可改变,且String内部没有提供修改字符数组中元素的方法;String类也被final修饰,使得无法通过继承覆盖这些方法来改变字符数组元素。其他两个没有使用final修饰;所以String是不可变的,其他两个是可变的。
- StringBuffer中的方法加了同步锁synchronized,所以是线程安全的。StringBuilder中的方法没有使用同步锁,且是可变的,所以是线程不安全的。相比而言,StringBuilder效率高。
- 对于
改变String的操作,会生成新的String对象。
结论:
- 少量
改变String的操作时,使用String - 单线程操作可变字符串时,使用StringBuilder
- 多线程操作可变字符串时,使用StringBuffer
hashCode() 和equals()
- equals()是Object类的方法,所以每个对象都有这个方法。Object中的equals()判断两个对象是否是同一个对象,等效于==。
- equals()方法默认:比较引用。String重写了equals(),判断两个引用对象的值是否相等。
- hashCode()获取对象的哈希值。
- 重写equals()必须重写hashCode()。原因:首先,自定义类继承自Object类的equals()比较的是两个对象的引用,为了比较自定义类所含值是否相同,需要重写equals()。其次,考虑到该类对象被存入集合的可能:对于使用散列的集合而言(HashMap、HashSet(hashSet底层使用了hashMap)等),存入对象作为Key时,会获取对象的哈希码,通过哈希码找到对应的存储位置;然后再通过对象的equals()判断是否有重复对象。而继承自Object类的hashCode()在运行时对于每个对象会生成不通的哈希值,那么拥有相同值的对象就可能不会放在同一个索引下(因为(jdk1.6)HashMap使用对象的哈希值和数组容量进行与操作的结果作为对象在数组中的索引,所以不同的哈希值有可能得到相同的索引值)。这种情况下,拥有相同值的对象就会存入集合中。为了通过equals()去除“相同值”对象,就要保证拥有相同值的对象的哈希值是一样的,所以需要重写hashCode()。
重写原则
- equals()相等,hashCode()必须相等:只有在数组中的索引相同才会进行equals判断。
- hashCode()相等,equals()不一定相等:(jdk1.6)HashMap通过对象的哈希值和数组容量进行与操作,所以不同的哈希值可能有相同的索引位置。发现jdk1.8的计算索引方式不一样,所以:假设hashCode()不同,对象的索引也不同,那么就要保证equals()和hashCode()返回值一致。
null
- null可以强制转为任意引用类型,表示该类型的空对象。
- 空对象可以访问该类型的 static可见 属性/方法 eg:((ClassA)null).fun();//ClassA有一个static的fun()。可以访问成功
类型转换
- 等级(由小到大):byte(8位)<short(char)(16位)<int(32位)<long(64位)<float(32位)<double(64位)。
- 小到大会自动转换,大到小需要强制转换。在转换的过程中,可能存在精度丢失。
- short和char都是16位,但是short表示的是有符号整数,char表示的是Unicode字符,所以依然需要强转。
- byte,short,char在运算时会自动转为int,所以结果为int,需要强转。但是使用+=等复合赋值运算时,会自动进行类型转换。
- 基本类型之间存在自动类型转换规则,但是不能从基本类型直接转换为其他基本类型的包装类型:比如,可以double d=12;不可以Double d=12。
- 2个final数值类型进行算术运算,结果会自适应左边数值类型。eg:final short a=1;final int b=2;byte c=a+b。
- 0.9f==0.9为false,二进制无法精确表示0.9,在进行运算时存在误差。这里进行了float向double的进制转换。
- 二进制无法精确表示0.1,所以下列代码可能无限循环。因为:小数的2进制:乘2取整,直到小数部分为0。eg:0.125的二进制为0.001(2),而0.1的2进制是无限循环的。对于不可精确表示的小数,Java会输出最接近这个数的一个值,在误差足够小的时候结果看上去是精确的,但不精确才是常态。
for(double i=9;i!=10;i+=0.1){ System.out.println(i); }
- 方法重载时的优先原则
private void funOne(int i){ } private void funOne(Integer i){ } private void funOne(double d){ } private void play(){ /* 1是int类型,所以优先匹配funOne(int i);如果没有则匹配可以直接类型转换的方法,这里是funOne(double d);如果没有则匹配可以装箱的方法,这里是funOne(Integer i). */ funOne(1); }
try-catch-finally中的return
- 如果3个return都执行了,那么执行顺序为:try->catch->finally。前面的return会先把值压入栈中,后面的return会覆盖栈中的值。
- try里面的return在try代码块发生异常时不会执行;catch里面的return在try代码块发生异常时才会执行;finally里面的return无论怎样都会执行。于是有了3。
- 在一个有返回值的方法中,return出现的规则:保证有效的return有且只有一个。比如:1. finally里有return,那么肯定会执行,就不能在方法最后写return。2. 如果只在try里有return,这个return不一定会执行,所以必须在方法最后写return。
接口和类的修饰符
- 通常情况下,类的修饰符有:default、public、abstract、final;接口的修饰符有:default、public
- 局部变量不能被访问修饰符修饰,不能为static。
- 抽象方法不能被private、static、synchronized、native修饰。
JSP(知道是啥就行)
- JSP就是Servlet。第一次访问JSP时,会被web容器转译、编译、加载为servlet,jsp init()执行一次;如果JSP有变动,会重新生成servlet。
- 每次请求会调用jsp service()。
String常量
String s=new String("hello");
- 字符串常量池是否有"hello",没有就创建一个。
- 在堆内存中新建一个String,从常量池中拷贝"hello"值。即:创建了2个"hello"。
基本类型和包装类
- 基本类型和包装类进行 == 运算时,包装类会自动拆箱。
- int型,-128~127会存在常量池中。(计算机中运算的是补码)
Integer b=23;//自动装箱 Integer c=23; Integer d=new Integer(23); System.out.println(b==c);//true System.out.println(b==d);//false Short s=23;//自动装箱 System.out.println(b.equals(s));//false
- new的话,会创建新的对象,所以b和d不相等
- 基本类型和包装类型进行==比较,包装类型自动拆箱,也就是值的比较
- 对于-128到127之间的int、short,小于127的byte、char自动装箱的包装类,是相等的
- equals方法,先判断是否是相同类型,再比较值
- 正整数/0为正无穷大,0/0或负数的平方根为NaN(not a number),判断一个x(假设double类型)是不是一个数:Double.isNaN(x)
- 当参与/运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法。
位运算
- n=-n-1。
- 原码、反码、补码
- 正数的三码一样。
- 负数的反码:原码的符号位不变,其他取反。
- 负数的补码:反码最后一位+1。
- 移位运算
- “>>”
- 负数:高位补1
- 正数:高位补0
- “>>>” 高位补0
- 对char、byte、short进行移位运算时,会先转为int型,那么最终结果也是int型。
- 移位运算右操作数(移多少位)只有低5位有效,也就是模32后进行移位。
- “>>”
Java字符
只有一种存在形式:Unicode
- 在jvm、内存、char、string类型的变量中,字符都是以Unicode形式存在。
- JVM内部统一使用Unicode表示,在JVM之外(文件系统)就进行了编码转换。
- 所有的I/O基本分为2类:
- 面向字符的输入/输出流:读入JVM内部的内容依然是Unicode形式表示,但是输出时,隐式地做了编码转换:将Unicode转换为系统默认的编码方式。
- 面向字节的输入/输出流:保证系统中的文件二进制内容和读入JVM内部的二进制内容一致 面向:处理输入/输出时,在哪个层面保持一致。
基本运算符
- ++
结果:j=0。Java用了中间缓存变量的机制,j=j++等价于:int j=0; j=j++;
temp=j; j=j+1; j=temp;
- +=和+
shrot a=1; // a+=1;等价于a=(short)(a+1);所以可以成功 a+=1; // 由于a+1结果自动转换为int型,所以赋值失败。 a=a+1;
- 左移
移位操作符右边的参数要先模32。所以a>>32等价于a>>0,所以不变。int a=32; a=a>>32;
- 三元操作符的类型务必保持一致
三元操作符的类型转换规则:int i = 80; String s1=String.valueOf(i<100?90:100); String s2=String.valueOf(i<100?90:100.0); System.out.println(s1.equals(s2));//false 结果:s1="90",s2="90.0";
- 两个操作数不可转换,则不做转换,返回值为Object类型。
- 两个操作数是明确类型的表达式(比如:变量),则按照正常的类型转换规则自动转换:byte<short(char)<int<long<float<double;若两个操作数为short和char,会转换为int型
- 两个操作数一个是数字S,一个是表达式(类型为T)。
- S在T范围内,则转换为T类型。
- S不在T范围内,则T转换为S类型。
- 两个操作数都是数字,则返回值类型为范围较大者。
- &&优先级高于||
Java异常
异常:导致程序正常执行流程中断的事件。分为:可查异常(必须处理)、非可查异常(不必处理)。
- Error:JVM生成并抛出
- 死循环
- 内存泄漏:内存未释放而不能使用。
- Exception
- 运行时异常:由程序错误导致的异常
- 其他异常:程序本身没有问题,由于像I/O错误这类问题导致的异常
- 未检查(unchecked)异常:Error和运行时异常;已检查(checked)异常:其他异常。
- 编译器会检查是否为所有的checked异常提供了异常处理器。
- 子类覆盖父类方法时,如果父类方法没有抛出任何checked异常,子类也不能抛出任何checked异常;如果父类抛出了checked异常,子类要么不抛出任何异常,或者抛出的checked异常是父类抛出的checked异常或其子类。
传值还是传引用
- 基本数据类型,Java传递值的副本。
- 对象类型,Java传递对象引用的副本。
序列化
序列化:把对象的状态转换成一组byte,只能保存对象的非静态成员变量的值。
- 作用:把对象的状态保存起来,以便后续使用。
- 断点续传中,把代表传输进度等信息的对象,在停止传输时序列化,下次传输前反序列化,就可以获得传输进度。
- HOW
- 要序列化的对象必须实现Serializable接口(没有任何需要实现的方法)
- 使用ObjectOutputStream的writeObject方法进行序列化
- 使用ObjectInputStream的readObject方法进行反序列化,获得的对象是Object类型,需要进行显式转换。
- transient关键字
- 作用:控制对象变量的序列化,所修饰的变量不被序列化。反序列化时,不会调用类的构造方法,所修饰的变量被设置为默认值:int设为0,引用类型设为null。
- 只能修饰变量。
- 序列化ID
- 职责:代表类的版本
- 作用:在反序列化时,如果ID不一致,则认为类被修改过了,那么反序列化失败。
for循环省略大括号时的错误
public void test() {
for(int i=0;i<10;++i)
Integer k=new Integer(0);
System.out.println(i);
}
解释:编译出错。这里k的作用域是整个方法,这样就出现重复定义的错误。
Java内存管理
- 内存泄漏:对象分配的内存始终没有被释放。这些对象有如下特点:
- 对象是可达的
- 对象是无用的。(后续不再使用这些对象)
- 调用System类的静态gc()方法可以运行垃圾收集器,但并不能保证GC一定会执行,也不能保证立即回收指定对象。
- 判断一块内存是否符合GC收集标准的条件:
- 给对象赋予了空值null,以后再没有调用过
- 给对象重新分配了内存空间
一块内存符合GC收集标准,并不意味着这块内存一定会被回收。
概念
- JavaSE1.4开始强制程序入口main方法是public的。
- Java不需要sizeof,因为Java程序最终是运行在JVM上,所有类型的大小都是固定的。不受机器、系统的影响。
- goto、const是Java保留的关键字,但未使用。
- 标签+break、continue。标签:后面跟有冒号的标识符
xiang: for(int i=0;i<10;++i) { for(int j=0;j<10;++j) { System.out.println(i+" "+j); // break xiang;//可直接结束双循环 continue xiang;//结束内循环 } }
- switch
- byte、char、short、int(或对应包装类)、枚举常量、String字面量(jdk1.7开始)
- default中的break没有意义
- 找到对应的case后,若没有break,则简单下移,处理后续的case.
// 结果:3 4 switch(3) { case 1: System.out.println(1); case 2: System.out.println(2); case 3: System.out.println(3); case 4: System.out.println(4); break; default: System.out.println(0); break;//可不写,因为已经到末尾了 }
- finalize()
- 作用:对象不可达时,GC调用该对象的finalize()。下次GC时若不可达就回收内存,否则对象“复活”。
- 垃圾回收只与内存有关
- 覆盖finalize(),手动回收本地方法(C、C++)创建的对象。
- clone方法与cloneable接口(原型模式)
- clone方法是Object类定义的,所以每个对象都拥有该方法。
- cloneable接口是一个标记接口。只有实现了该接口的类的实例才可以调用clone方法。若未实现该接口就调用会抛异常CloneNotSupportedException
- 集合中线程安全的是 喂(vector)S.H.E.(stack、hashtable、 Enumeration )
- 内部类(可以被abstract或final修饰)
- 成员内部类可以访问外围类的所有资源,但是本身内部不能有static成员,成员内部类对象拥有一份创建了它的外围类对象的引用,可以通过Outer.this拿到对外部类对象的引用(编译器为成员内部类创建了默认构造函数,把外围类的引用传给内部类),不会再次创建外围类对象。out.new Inner();创建成员内部类对象。下面的in1和in2所引用的外围类对象是同一个。
Outer out=new Outer(); Outer.Inner in1=out.new Inner(); Outer.Inner in2=out.new Inner();
- static内部类只可以访问外围类的static资源。不拥有外围类对象的引用,如果内部类不需要访问外围类的对象,应该使用静态内部类。
- 局部内部类不能被访问修饰符、static修饰,可以被abstract、final修饰。jdk8之前只能访问外围类的final修饰的成员或形参。
- 匿名内部类
- 没有构造方法
- 没有静态资源
- 不能被访问修饰符、static修饰
- 只能创建匿名内部类的一个实例
- 可以继承类或实现一个接口,但不可兼备。
- 注:
- 接口中的成员默认是public static,所以接口中的内部类只是位于接口的命名空间下,且可以实现外围接口。
- 内部类嵌套的层次没有限制
- 为什么用内部类?
- 提供了进入外围类的接口
- 每个内部类可以独立地继承一个类,不论外围类是否继承了这个类,对于内部类都没有影响,从而可以实现“多继承”
- 内部类无法被覆盖,基类和派生类中的同名内部类是相互独立的。
- 内部类被编译器生成外围类名$内部类名.class文件,JVM不知道这是个内部类。
- 成员内部类可以访问外围类的所有资源,但是本身内部不能有static成员,成员内部类对象拥有一份创建了它的外围类对象的引用,可以通过Outer.this拿到对外部类对象的引用(编译器为成员内部类创建了默认构造函数,把外围类的引用传给内部类),不会再次创建外围类对象。out.new Inner();创建成员内部类对象。下面的in1和in2所引用的外围类对象是同一个。
- enum不能被继承(构造方法是private)
- enum类不存在final或者abstract的,就是不能被继承。编译后生成的class才有final、abstract的说法。
- 对象会一次性同时创建。
- 如果enum类中没有抽象方法,也没有匿名子类对象,那么这个类是被final修饰的。
- 如果enum类中有抽象方法,那么这个类是抽象的,对象必须实现该抽象方法。
- 如果enum类中没有抽象方法,但是有匿名子类对象,那么这个类不被final修饰,且不是抽象的。
- 创建对象时,构造方法不一定被调用:1.通过反序列化创建对象时,不会调用构造方法。2.通过对象的clone()复制一个对象时,不会调用构造方法。
- 类之间的关系 在类之间,常见的关系有:依赖(uses-a)、聚合(has-a)、继承(is-a)。
- 本地方法可以绕过Java语言的存取控制机制。
- 静态导入(Java SE5.0开始):import static java.lang.System.*;就可以直接使用System类的静态方法和静态域。
- Java7引入了“带资源的try”构造,自动调用实现了Closeable接口的对象的close();
try(OutputStream out=new FileOutputStream("/home/xiang/workspace/myFile/demo.txt")){ out.write("hello".getBytes()); }catch(IOException e){ System.out.println(e.getMessage()); }
- 方法名和参数列表是方法的签名,覆盖一个方法必须要有相同的签名。返回类型要保证兼容,可以是父类方法返回类型的子类;子类方法的可见性不能小于父类中的方法;静态方法不会被覆盖。下面的例子中,Son的play方法并没有覆盖父类的play方法,这两个方法在内存中占用了不同的地址空间,不存在冲突的问题,具体要执行哪一个,要看是由哪个类来调用的。
class Father{ public static void play(){ System.out.println("hello"); } protect A show(){ System.out.println("A"); } } class Son extends Father{ public static void play(){ System.out.println("world"); } //B是A的子类 public B show(){ System.out.println("B"); } }
- 编码
- ASCII:1个字节,最高位设置为0.
- ISO8859-1:又称Latin-1,一个字节,0~127和ASCII一样。(被Windows-1252取代)
- GB2312:简体中文,2个字节,2个字节最高位都是1。
- GBK:兼容GB2312,2个字节。
- GB18030:兼容GBK(2个字节)。2个或4个字节。
- Big5:繁体中文,2个字节。
- Unicode:只规定了每个字符对应的数字编号,没有规定对应的二进制表示。(把Unicode编号对应到二进制表示:UTF-32:4个字节,UTF-16:2个或4个字节,UTF-8:变长字节-英文1个字节,中文大多3个字节)
- 编码转换:借助Unicode编号进行编码转换,改变了字符的二进制内容,但是没有改变字符的脸。
- assert断言
- 开启断言:添加vm参数:-ea
- assert boolean表达式,如果表达式结果为false,就会报错。如果希望自定义报错时提示信息,加一个表达式。assert boolean表达式:提示信息。
- strictfp
- strict float point的缩写,用来确保浮点数运算的准确性。
- 当一个类被strictfp修饰时,所有方法都会自动被strictfp修饰。
- for each循环
for(p:collection){//collection必须是一个数组或者是一个实现了Iterable接口的类的实例 //语句。 } for(int[]a:aa){//按照一维数组处理,当aa为一个多维数组时,取出的还是一个数组 }
- Java中,有数组int[] a;无法通过a+1访问数组下一个元素。Arrays.toString()打印一维数组,Arrays.deepToString()打印二维数组。
- 构造器总是伴随着new操作符的执行被调用。
- 只能使用星号(*)导入一个包。
- super不是一个对象的引用,只是一个指示编译器调用超类方法的特殊关键字
- 方法存在覆盖时,其调用是动态绑定;重载方法、private方法、static方法、final方法在编译器绑定(静态绑定)
- 如果将一个类声明为final,那么这个类中的方法自动成为final,不包括域
- String[] s="".split(",");//split:没有找到分割点的话,返回一个包含原值的String数组。s.length为1
字符串
- 字符串字面值会存放在字符串常量池中。
- String类型的substring方法:如果结果和原字符串一样,返回原字符串的引用;否则,new一个字符串对象。
- 2个字符串字面值相加,如果常量池中有就直接引用;否则,在常量池中保存一个。
- String类型引用+字符串字面值,会创建一个新的对象。
- 字符串的子串数:n*(n+1)/2+1。
Servlet
- 实例化:对应该Servlet的URL第一次被请求时,web容器会调用Servlet的init()进行实例化(也可以设置应用程序启动时实例化),在手动销毁/应用程序停止之前,该Servlet实例一直存在容器中。之后所有对应该Servlet的请求不再创建新Servlet实例,web容器会为每个请求创建一个线程,这些线程共享该Servlet。
- 线程调用Servlet的service(),根据HTTP请求方法调用对应的方法。
- 在手动销毁/应用程序停止前,会调用Servlet的destroy(),随后Servlet的资源会被GC回收,该Servlet的生命周期也就结束了。
反射
运行时,获取类型的信息(接口信息、成员信息、方法信息等),根据这些信息创建对象、访问/修改成员、调用方法等。反射的入口是名称为Class的类。
- Class类是一个泛型类,有一个泛型参数,getClass()返回Class<?>。
- Class类有一个静态方法forName,可以根据类名直接加载Class获取Class对象。
- 有了Class对象后,就可以获得关于该类型的很多信息。
- isAssignableFrom(Class<?> cls):判断参数类型cls能否赋给当前Class类型的变量。
- 注:String[]、int[]...不是Object[]的子类,是Object的子类。
static final变量param
- param是“编译期常量”,比如param="hello";那么不用初始化这个类就可以直接访问,构造代码块不会执行。
- static变量子类和父类公用。
深拷贝浅拷贝
- 浅拷贝:只会出现在可变引用类型上(String类型浅拷贝也没有问题),拷贝的只是引用,实际对象还是原来的对象。值类型/基本类型不存在浅拷贝。
- 深拷贝:针对引用类型的,拷贝引用的同时,把原有对象也拷贝一份,这样2个对象之间的修改互不影响。
- 要进行深拷贝,必须:实现Cloneable接口(标记接口,唯一目的是可以用instanceof进行类型检查),才能调用继承自Object类的clone(),但默认的clone()是浅拷贝,所以必须重写(super.clone()拷贝自己,调用引用类型的clone并设置到自己属性上)。
@Override
public Object clone() throws CloneNotSupportedException {
Person1 p=(Person1)super.clone();
p.car=(Car)this.car.clone();
return p;
}
泛型
参数化类型。
- 为什么用:主要是为了代码重用。为什么不使用Object类型作为参数呢?因为使用Object类型作为参数有如下缺点:1、对于一个以Object类型为参数的方法来说,可以传入任何类型的对象,限制性不够。2、在get操作时返回的是Object类型对象,需要强制转换,强制转换有可能出错。
- 怎么用:
泛型类 class P<T>{ private T good; public T getGood(){ return good; } } 泛型方法 class P{ public <T> void getGood(T good){ //操作 } } 类型变量的限定 T extends A:A的子类型 T extends A&B:A和B的子类型,因为一个类只能继承一个类,所以类限定只能有一个,且必须是限定列表中第一个。
- 类型擦除 泛型只是编译期存在,在运行时JVM里泛型会被擦除。T会被替换为Object类型,T extends A、T extends A&B被替换为A类型(编译器会自动插入强制类型转换)。
- 约束与限制
- 不能用基本类型作为类型参数
- 运行时类型查询只适用于原始类型
- 不能抛出也不能捕获泛型类异常实例
- 参数化类型的数组不合法
- 不能实例化类型变量
- 泛型类的static上下文中类型变量无效
- 泛型类型的继承规则
- 尖括号里的类表示一个点;带有?表示一个范围。
- List<?>和List是等价的,都代表最大范围。
- 所有点之间无法互相赋值,除非是两个相同的点。
- 小范围可以赋值给大范围;点可以赋值给包含它的范围。