[TOC]
一、面向对象
面向对象的特征: 抽象(注意与当前目标有关的,选择一部分,暂时不用部分细节,分为过程抽象、数据抽象) 继承:联结类的层次模型、允许和鼓励类的重用,派生类可以从它的基类那里继承方法和实例变量,进行修改和新增使其更适合 封装:封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面,这些对象通过一个受保护的接口访问其他对象 多态:允许不同类的对象对同一消息作出响应,包括参数化多态性和包含多态性,灵活、抽象、行为共享、代码共享,解决程序函数同名问题
二、基础类型及其包装类型
基本类型 | boolean | byte | char | short | int | float | long | double |
---|---|---|---|---|---|---|---|---|
包装类型 | Boolean | Byte | Character | Short | Integer | Float | Long | Double |
位数 | 1 | 8 | 16 | 16 | 32 | 32 | 64 | 64 |
字节数 | 1 | 2 | 2 | 4 | 4 | 8 | 8 |
-
字符集
unicode是字符集,一种标准,UTF-8、UTF-16、GBK之类的是编码方式,是字符集的具体实现
UTF-16:定长,固定2字节, UTF-8:变长,中文占3字节,英文占1字节
char可以保存一个中文字符
java中采用unicode编码,无论中文、英文都是占2个字节
java虚拟机中使用UTF-16编码方式
java的字节码文件(.class)文件采用的是UTF-8编码,但是在java 运行时会使用UTF-16编码。
-
自动转换的顺序,
由高到低:btye, short, char(这三个之间无法自动转换,只能强转) —> int —> long —> float —> double
-
为什么 占8字节的long 转占 4字节float 不需要强转转化?
因为底层实现方式不同,浮点数在内存中的32位不是简单地转化为十进制,而是通过公式计算得到,最大值要比long的范围大
装箱和拆箱
以 int 和 Integer 为例
-
装箱的时候自动调用的是 Integer 的 valueOf(int) 方法,Integer a = 1; 就会触发调用valueOf(int)方法
拆箱的时候自动调用的是Integer的 intValue() 方法
parseInt("")方法是将字符串转化为基本类型
其中,装箱时,即调用 valueOf() 方法时,会先去缓存池里找,看看该值是否在缓存池的范围中,如果是,多次调用时会取得同一对象的引用,属于同一对象,如果不在缓存池中,则使用new Integer()
使用new Integer()初始化的,无论传入的数是否在缓存池的范围内,都是重新分配内存初始化的,属于不同对象
|
|
-
基本类型缓存池,其他类型没有
boolean:true、false
short : -128 ~127
int : -128 ~127
char : \u0000 ~ \u007F
-
equals 和 == 对于 装箱和拆箱
-
== :比较的是两个包装类型的,同包装类型,比较两者的引用,判断是否指向同一对象;不同包装类型用==比较会出现编译错误
比较时两者都是基本类型,无论类型是否一样,都是比较数值
比较时一个是包装类型,一个是基本类型,无论值的范围是否在缓存池内,则将自动拆箱,比较基本类型
比较时有算术运算,自动拆箱,运算,比较数值
-
equals:看具体类型的equals方法,一般先比较类型,类型不一样直接返回false,类型一样再比较数值
比较时传入基本类型,会进行装箱,之后进行equals比较
比较时有算术运算,自动拆箱,运算,之后根据运算完后的类型再装箱(可能会向上转型),之后再进行equals比较
-
运算与转型
- 从低位类型到高位类型自动转换;从高位类型到低位类型需要强制类型转换
- 算术运算中,基本就是先转换为高位数据类型,再参加运算,结果也是最高位的数据类型
- short、byte、char计算时都会提升为int
- 采用 +=、*= 等缩略形式的运算符,系统会自动强制将运算结果转换为目标变量的类型
- 当运算符为自动递增运算符(++)或自动递减运算符(–)时,如果操作数为 byte,short 或 char类型不发生改变
- 被final修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型自动转化
- 三元运算符?,:两边是基本类型会做自动类型提升
|
|
三、String
1.基本
-
底层:private final char value[];
value数组被final修饰,因此当它初始化之后就不能再引用其它数组
String 内部没有改变 value 数组的方法,因此可以保证 String 不可变
String类的方法都不是在原来的字符串上进行操作,而是重新生成新的字符数组
-
类本身被 final 修饰,使它不可被继承
-
String类的 “+" 本质是使用StringBuilder的append方法,最终返回new的string
-
JDK9之前底层使用char数组,JDK9底层改为使用byte数组+coder标识符,主要是因为有些字符集并不需要使用到2个字节的char,不需要用到2个字节的char
2.不可变的好处
-
可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
-
String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
-
安全
-
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
-
不可变得特性使得它天生是线程安全的
-
3.字符串常量池(String Pool)
- 一般用于缓存字符串字面量和符号引用
String的intern方法可以把相应的字符串缓存起来,保存在永久代中,但是一般永久代空间有限,且只有FullGC才会进行垃圾回收(JDK8后移永久代移到了元空间中,默认大小也增加了),所以需要谨慎使用该方法
4.StringBuffer和StringBuilder
StringBuffer是线程安全的,因为大部分方法都用了synchronized关键字修饰,而StringBuilder没有,所以是非线程安全的,两者在功能上是等价的,性能上,StringBuilder比StringBuffer好。
3和4具体参考深入理解Java中的String,这篇文章写得相当详细了
四、equals()、hashCode()、clone()
equals()
- 在不重写的情况下,Object类下的 equals() 方法比较的是两个对象的引用,即判断两个对象是否是同一个对象,此时等价于 ==
- 重写的情况下,看具体类重写后的equals方法,像String类的 equals 方法是先判断是否是String类型,再比较字符的内容
hashCode()
-
作用是返回对象的int类型的哈希码,一般用于当索引,例如,在HashMap里,加入一对键值对,HashMap会先计算key的哈希值,取模,找到对应桶的下标
-
hashCode()一般会和equals()有联系,例如,HashMap在找到要加入的键值对所在对应的桶,桶内的键值对的哈希码肯定是一样,为了判断该键值对是否重复出现,将使用key的equals进行比较
为什么有了equals()还要hashCode方法,有了hashCode方法还要equals方法?
- 因为equals方法的实现比较复杂,效率较低,hashCode只需计算hash值就能进行对比,效率高
- hashCode方法不一定可靠,不同对象生成的hashCode可能一样,所以需要equals方法
hashCode() 与 equals() 的相关规定
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
clone()
-
clone分为深拷贝和浅拷贝
-
浅拷贝:对象的属性是基本类型的,直接复制一份;属性是引用类型的(如数组、对象),复制引用,也就是拷贝过后的两个对象里引用类型的属性,都是指向同一个对象
Object类里的clone方法就是浅拷贝
-
深拷贝:对象里的属性无论是基本类型还是引用类型,都是重新复制一份,即会为拷贝对象里的引用属性重新开辟内存空间,不再是和被拷贝对象指向同一个对象
-
-
Object里的clone()方法被protected 修饰,一般类如果不重写的话是调用不了的,如果要重写的话,需要实现Cloneable接口,不然会抛出CloneNotSupportedException异常,之后可以通过super.clone()调用Object类中的原clone方法
关键字
final和static
- final关键字主要用在三个地方:变量、方法、类。
- 修饰变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,final修饰的变量一定要初始化,要不就直接初始化,不然就需要在每个构造方法里初始化,或者在代码块里初始化,代码块里的初始化和构造方法里的初始化只能二选一,否则会造成赋值重复
- 修饰类,表明这个类不能被继承,final类中的所有成员方法都会被隐式地指定为final方法
- 修饰方法,表明该方法不能被子类重写
- 声明为static成员不能被序列化
instanceof
- 是关键字,也是运算符
- 用于判断一个对象是否是 一个类的实例,一个类的子类,一个接口的实现类
抽象类和接口
-
抽象类可以有构造方法(但不能实例化),接口没有
-
抽象类可以有普通成员变量、静态变量或常量,访问类型任意,接口只有静态常量,且默认被public static final修饰
-
抽象类可以包含非抽象的普通方法,接口中的方法必须是抽象的,不能有非抽象方法
-
抽象类中的抽象方法访问类型可以是public、protected和不写,接口中的方法默认是public abstract
-
抽象类中可以包含静态方法(可以调用),接口不行
-
JDK1.8之前接口中的方法不能有方法体,且不能被default修饰,但是可以不写访问修饰符,
1.8之后(包含1.8)接口中的方法可以有方法体,表示默认方法体,但需要用default修饰
-
JDK1.8之前接口中的方法不能用static修饰,1.8之后(包含1.8)才可以使用static修饰,才可以通过接口名调用静态方法
继承
重写与重载
-
重写(Override)
继承中,子类重写父类方法
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
- 子类抛出的异常比父类的小或者相等
关于静态方法重写
static方法不能被子类重写,子类如果定义了和父类完全相同的static方法,Son.staticmethod()或new Son().staticmethod()都是调用子类的,如果是Father.staticmethod()或Father f = new Son(); f.staticmethod()调用的是父类的
-
重载(Overload)
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同
注意,返回值不同,其它都相同不算是重载。
初始化顺序
(括号内的按出现先后进行初始化)
- 父类(静态变量、静态代码块)
- 子类(静态变量、静态代码块)
- 父类(实例变量、普通代码块)
- 父类(构造函数)
- 子类(实例变量、普通代码块)
- 子类(构造函数)
在构造方法中,this()和super()调用构造方法时,只能放在第一行,且不能同时出现
内部类
基础
-
内部类是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员,并且依附于外部类而存在的。
-
内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。
-
内部类主要有以下几类:成员内部类、局部内部类、静态内部类、匿名内部类
成员内部类
-
在外部类里想调用内部类里的方法和变量,只能通过new的形式创建内部类实例才可以使用
-
内部类调用外部类的方法,无论静态非静态,直接调用
-
内部类里的成员可以和外部类的成员的名字相同,直接使用的时候调用的是内部的,内部类调用外部类同名变量需要加上外部类名,如果没有同名变量就可以直接使用,不需要加外部类名
-
在其他地方想要调用有内部类的类的内部类方法,需要实例化一个外部类,再使用外部类的实例变量实例化内部类
-
可以被abstarct修饰,但无法实例化
-
不能有静态成员、方法
1 2 3
Outer out = new Outer(); Outer.Inner outin = out.new Inner(); outin.inner_f1();
局部内部类
- 局部内部类调用外部类的方法,直接调用
- 外部类调用内部类方法,初始化外部类实例,调用有局部内部类的方法
- 局部内部类 可以看成是 方法里的成员变量,只能在该方法里实例化
- 局部内部类 对于同名变量的访问方式同成员内部类
- 方法外的外部类的成员变量可以直接访问,但只能访问内部类所在方法里的final修饰的变量
- 不能被static还有访问控制符修饰,可以被abstract、final修饰
静态内部类
-
静态内部类中可以定义静态或者非静态的成员或方法
-
静态内部类只能访问外部类的静态成员,不能访问外部类的非静态的成员
-
外部类方法访问内部类静态成员,直接 内部类名.静态成员变量
访问内部非静态成员,实例化内部类,在调用
说白了,静态属于整个类的,不属于某个对象的,可以直接使用,不依赖外部类,可以直接调用,或者实例化外部类里的静态类
1 2 3 4 5
Outer.Inner outin = new Outer.Inner(); outin.staticInner_f1(); // 或者直接调用 Outer.Inner.staticInner_f1() outin.inner_f1(); // 不能直接 Outer.Inner.inner_f1() System.out.print(outin.staticField + outin.field); // 同理只能 Outer.Inner.staticField, 不能Outer.Inner.field;
-
不可以只实例化外部类,再使用这个实例去实例化内部类或者调用内部类里的成员,这点跟成员内部类是不一样的
匿名内部类
- 匿名内部类不能有构造方法
- 无法被访问控制符、static修饰
- 匿名内部类不能定义任何静态成员、方法和类
匿名都没类名了,构造方法,静态成员之类的没办法调用了
反射和内省
- 反射:可以在运行时动态获取类信息或者动态调用类方法;JVM运行的时候,读入类的字节码到 JVM 中,对该类的属性、方法、构造方法进行获取和调用
类加载一次之后会在JVM中缓存,但是如果是不同的类加载器去加载同一个class则会多次加载。
两个类相等需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
反射慢是因为Java是静态语言,如果在JVM运行时才进行加载,进行参数和方法的解析,此时的JVM无法对反射加载的类进行优化,还有就是类加载需要经过验证,判断是否对JVM有害,反射加载验证会比平时在装载期的时间长,但是总的来说影响不大
- 内省:针对JavaBean,只能对Bean的属性进行操作;加载类,得到它的属性,对属性进行get/set,是对反射的一层封装
枚举
-
使用enum定义的枚举类默认继承了java.lang.Enum,而不是继承Object类
-
枚举类可以实现一个或多个接口
-
使用enum定义、非抽象的枚举类默认使用final修饰,不可以被继承,定义的Enum类默认被final修饰,无法被其他类继承
-
枚举类的所有实例都必须放在第一行展示,不需使用new 关键字,不需显式调用构造器。
自动添加public static final修饰
-
枚举类的构造器只能是私有的
-
枚举类也能定义属性和方法,可以是静态和非静态的
参考 java浅谈枚举类
具体例子
反编译之后会发现,SPRING、SUMMER、FALL这些是静态常量(public static final 修饰),而且是在静态代码块里初始化的,同时还附带有public static Season[] values()方法和public static Season valueOf(String s)方法
|
|
原理参考深入理解java类型
泛型
-
泛型会类型擦除,那它如何保证类型的正确?
java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的
只能在编译期保证类型相同
-
泛型被擦除,统一使用原始类型Object,泛型类型变量最后都会被替换为原始类型,那为什么我们使用的时候不需要强转转换?
比如ArrayList,它会帮我们进行强转转换,它会做了一个checkcast操作,检查什么类型,之后进行强转
-
类型擦除与多态导致冲突,如何解决
比如本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突,JVM采用桥方法解决此问题
-
泛型擦除的优点
类型安全,编译器会帮我们检查、消除强制类型转换,提高代码可读性、为未来版本的 JVM 的优化带来可能
-
List<?>、List、List、List<? super T>、List<? extends T>的区别
List<? super T>:T的父类,包括T,表示范围
List<? extends T>:T的子类,包括T,表示范围
List<?> 表示任意类型,如果没明确,就是Object或者任意类,与List、List一样,表示 点
List 也可表示范围
参考java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题
注解
序列化和反序列化
序列化:将对象转换为字节序列,用于将保存在JVM内存中的对象持久化,保存到文件或者网络传输
反序列化:将字节序列还原成对象
与JSON的比较,两者都可以用于网络传输,JSON更使用web方面、应用方面,易读,需要将JSON解析才能还原成对象,而序列化反序列化是JAVA提供的,由JVM来还原,应用范围会更广一些
除此之外也有其他协议,如基于XML文本协议(基于SOAP规范):WebService、Burlap,二进制协议:Hessian,Hessian生成的字节流简凑、跨平台、高性能比JDK的序列化反序列化优秀