Java基础
基本数据类型
java八大数据类型(各种编程语言给数据类型分配的内存大小,即位数,如果溢出则会不准):
- int 32位 4字节
- short 16位
- float 32位
- double 64位
- long 64位
- char 16位
- byte 8位
- boolean 1位
自动拆箱和自动装箱
自动拆箱:计算数值时,integer会自动转为int进行计算
自动装箱:int传入integer引用时,数值会包装为integer
int自动拆装箱只在-128~127范围中进行,超过该范围的两个integer的 == 判断返回false
编译器进行自动拆箱装箱,虚拟机运行的时候压根不知道这个。编译过程我们看不到
注意点:
- 1.首先Integer有缓存 会缓存-128到+128之间的数字,所以这之间的数字用==比较 都是相等的。“= =”比较的是引用地址,因为都是用的缓存中的数字,同一个数字地址肯定就是相同的。但是,如果你要new两个包装类,则返回false;
- 其次,“= =”在没有遇到算术运算的时候,不会触发自动拆箱,所以Integer e=321和Integer f=321 用“= =”比较 e= =f 的结果为false ,因为比较的是引用,这个是两个对象,自然引用就不同,但是在128范围内的数字,Integer内有缓存 就是一样的地址了。 在JDK1.5中提供了自动装箱与自动拆箱,这其实是Java 编译器的语法糖,编译器通过调用包装类型的valueOf()方法实现自动装箱,调用xxxValue()方法自动拆箱。自动装箱和拆箱会有一些陷阱,那就是包装类型复用了某些对象。
1.2 基本类型的存储方式
方法中的基本类型:存在虚拟机栈的局部变量表里,变量是一个引用,指向局部变量表里的值
类的基本类型成员变量:由于对象存在堆中,所以成员变量也在堆中,变量存的就是这个值
包装类对象:eg string.intern,会先在常量池找是否有该对象,有的话直接返回地址,即让引用指向常量对象的内存地址,Integer类中valueOf方法相同。所以基本数据类型的包装类型可以在常量池查找对应值的对象,找不到就会自动在常量池创建该值的对象。
基本类型的变量和对象的引用变量在函数的栈内存分配,new出来的则在堆。
声明基本类型时会先去栈中查找,new则不会先去堆里找,eg:
eg:int a=1;int b=1;a==b//true
Integer a = new Integer(1);Integer b = new Interger(1); a== b//false
Integer a = 1;Integer b = 1; a==b//true
二.常量池
jdk1.7之后的常量池被放在了堆空间。
相同点:intern方法先去查询常量池,如果找到即返回
不同点:若在常量池未找到,则不会copy到常量池,而是在常量池里生成一个对原字符串的引用
String s = "1";
先去字符串常量池检查,如果不存在,则在常量池开辟内存存放“1”;如果存在,则在栈中开辟空间,引用存放常量池中值的内存地址。
String s = new String("1");
先去字符串常量池检查,若不存在,则在常量池中开辟内存空间存放;如果存在,则不重新开辟空间,然后在堆中开辟空间存放new出的String对象,在栈中开辟空间,存放堆中String的内存地址,引用指向new出的对象。
常量池推荐大佬博客
三.String
String的连接:
表达式只有常量时,编译器完成计算;表达式有变量时,运行期才计算,所以地址不一样。eg~
- String s4 = “a” + “a”
- String s5 = s1 + s2;
intern():若常量池内有,则返回常量池内的地址;若常量池内没有,则1.7之后返回类对象的引用地址。
equals():比较字符串内容是否一致。
StringBuffer和StringBuilder:
底层继承父类的可变字符数组value,初始化容量为16,底层都是利用可修改的char数组(JDK 9 以后是 byte数组)。
除了hash这个属性其它属性都声明为final,因为它的不可变性,所以例如拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。
StringBuffer在大部分涉及字符串修改的操作上加了锁,保证线程安全,效率较低。
★所以如果我们有大量的字符串拼接,如果能预知大小的话最好在new StringBuffer 或者StringBuilder 的时候设置好capacity,避免多次扩容的开销。扩容要抛弃原有数组,还要进行数组拷贝创建新的数组。
String在用+运算符时,先封装成StringBuilder,调用append,再toString返回,所以大量使用+操作时,应用StringBuilder代替String。
扩容:append方法调用ensureCapacityInternal,计算空间是否足够,不够则扩容,扩容长度为原来的两倍+2,若扩容后长度超过jvm支持的最大数组长度,则有两种情况。
- 新长度超过int最大值,则抛出异常;
- 否则直接使用数组最大长度作为新数组长度
删除:实际上是将剩余的字符重新拷贝到字符数组
常见场景:
- 在字符串不经常发生变化的业务场景优先使用String(代码更清晰简洁)。如常量的声明,少量的字符串操作(拼接,删除等)。
- 在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。不能使用String"+"来拼接而是使用,避免产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。如JSON的封装等。
- 在多线程情况下,如有大量的字符串操作情况,应该使用StringBuffer。如HTTP参数解析和封装等。
不可变性
首先final修饰的类只保证不能被继承,并且该类的对象在堆内存中的地址不会被改变。
但是持有String对象的引用本身是可以改变的,比如他可以指向其他的对象。
final修饰的char数组保证了char数组的引用不可变。但是可以通过char[0] = ‘a’来修改值。不过String内部并不提供方法来完成这一操作,所以String的不可变也是基于代码封装和访问控制的。
是一个常量,所以可以共享使用,节省内容。
equals与==
==是运算符,equals是方法。
基本类型比较:==比较两个 值是否相等,equals不能直接用于基本类型的比较,需要进行包装。
引用对象比较:==和equal都是比较栈内存的地址是否相等,我们可以根据自己的情况重写equal的方法,一般自动生成,去比较两个成员变量值是否相同。(String比较特殊)
equals相同的两个对象,hash码也应当相同!!!
四:常见关键字
final
- 修饰类:该类不能被继承。并且这个类的对象在堆中分配内存后地址不可变。但内部数据可以做改变,即改变实例中的值,但不改变内存地址
- 修饰方法:方法不能被子类重写,无法被继承。
- 修饰引用:引用无法改变,对于基本类型,无法修改值,对于引用,虽然不能修改地址值,但是可以对指向对象的内部进行修改
- 修饰基本数据类型变量和引用:一般情况下一定要被初始化,但遇到final int s;要求该变量必须在构造方法中被初始化,并且不能有空参构造器,所以要求每个实例都有不同的变量,且只被初始化一次,于是该变量就变为常量了;值不可变,引用不可变
与finally和finalize的区别:
- finally:放在 try…catch 的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。
- finalize:Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作,可以在垃圾收集期间进行一些重要的清除或清扫工作(如关闭流等操作)。但JVM(Java虚拟机)不保证此方法总被调用。
static
- 修饰变量:属于类变量,不需要创建实例对象就可使用;作用:如果有数据需要被共享给所有对象使用时,那么就可以使用static修饰。
- 修饰方法:声明独立于静态的方法,静态方法不能使用类的非静态变量;
静态方法从参数列表得到数据,然后计算这些数据;
assert
用于保证程序最基本、关键的正确性;检查通常在开发和测试时开启。为了提高性能,在软件发布后, assertion 检查通常是关闭的。
五.异常
为什么使用异常?
- 确保程序健壮性,提高系统可用率。
- 使代码阅读、编写和调试工作更加有序。
异常是什么?
- 指阻止当前方法或者作用域继续执行的问题。
- 当程序发生异常时,它强制终止程序运行,记录异常信息并将这些信息反馈给我们,由我们来确定是否处理异常。
- 异常是在执行某个函数时引发的,函数又是层级调用,形成调用栈,只要一个函数发生异常,所有的caller都会被影响,这些被影响的函数以异常信息输出时,形成异常追踪栈,最先发生的地方叫异常抛出点。
异常
异常:Exception以及他的子类,代表程序运行时发生的各种不期望发生的事件,可以被java异常处理机制使用,是异常处理的核心;
非检查异常,运行期异常:
- Error 和 RuntimeException 以及他们的子类;
- 编译时,不会提示和发现这样的异常,不要求在程序处理这些异常;
- 可以tryatch处理,也可以不处理(让jvm去处理);
- 应该修正代码,而不是去通过异常处理器处理(数组越界);
- 一般直接使用throws交给jvm处理即可。
检查异常,编译器异常:
- 除了Error 和 RuntimeException的其它异常;
- 强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws),否则编译不通过;
- 一般是由程序的运行环境导致
检查和非检查是对于javac来说
错误
错误:Error类以及他的子类实例,代表JVM错误(创建特别大的内存)。必须修改代码,要不永远错误
异常处理
异常调用链
为什么?
在一些大型的,模块化的软件开发中,一旦一个地方发生异常,则如骨牌效应一样,将导致一连串的异常。假设B模块完成自己的逻辑需要调用A模块的方法,如果A模块发生异常,则B也将不能完成而发生异常。
但是B在抛出异常时,会将A的异常信息掩盖掉,这将使得异常的根源信息丢失。异常的链化可以将多个模块的异常串联起来,使得异常信息不会丢失。
异常链化!
以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫他根源异常(cause)。
查看Throwable类源码,可以发现里面有一个Throwable字段cause,就是它保存了构造时传递的根源异常参数。这种设计和链表的结点类设计如出一辙,因此形成链也是自然的了。eg~
public class Throwable implements Serializable {
private Throwable cause = this;
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
}
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
//........
}
自定义异常
如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。
- 一个无参构造函数。
- 一个带有String参数的构造函数,并传递给父类的构造函数。
- 一个带有String参数和Throwable参数,并都传递给父类构造函数。
- 一个带有Throwable参数的构造函数,并传递给父类的构造函数。
当finally遇上return问题
try…catch…finally中的return 只要能执行,就都执行了,他们共同向同一个内存地址(假设地址是0×80)写入返回值,后执行的将覆盖先执行的数据,而真正被调用者取的返回值就是最后一次写入的。
- finally中的return 会覆盖 try 或者catch中的返回值。
- finally中的return会抑制(消灭)前面try或者catch块中的异常。
- finally中的异常会覆盖(消灭)前面try或者catch中的异常
注意事项
1.当子类重写父类的带有 throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws方法 。这是为了支持多态。
例如,父类方法throws 的是2个异常,子类就不能throws 3个及以上的异常。父类throws IOException,子类就必须throws IOException或者IOException的子类。
2.Java程序可以是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码处理的异常 会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。
也就是说,Java中的异常是线程独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。
六.内部类
内部类是什么?
- 指在一个外部类的内部再定义一个类。可以将一个类定义在另一个类里面或者一个方法里面。
- 作为外部类的一个成员,并且依附于外部类而存在。
- 可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)
- 可以无条件的访问外部类的所有成员属性和方法,包括private和静态成员。因为当某个外围类的对象创建内部类的对象时,此内部类会捕获一个隐式引用,它引用了实例化该内部对象的外围类对象。通过这个指针,可以访问外围类对象的全部状态。
内部类分类
成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部。
使用:
间接使用:在外部类方法当中,使用内部类
直接:外部类名.内部名称。。。。。
内部类成员变量:this.xxx ;外部类成员变量:外部类.this.xxx
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。因为属于一个变量,所以不能用public等修饰。(包含匿名内部类)。
重点:局部内部类访问方法的局部变量,在1.7之前是需要讲局部变量进行final修饰;为什么?因为局部变量在java栈的变量表里面,而new出来的方法是在堆内存,方法结束局部变量消失,而堆内存一直存在,所以要留一个变量值;
静态内部类
加static的,不用依赖外部类,不能访问非static属性。
匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
使用情况:如果接口的实现类就实现一次,那么可以省略这个实现类,直接用匿名内部类
内部类共性
内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号
内部类不能用普通的方式访问。如果存在和外部类相同的属性,发生隐藏情况,需要外部类.this成员。
静态内部类不能随便的访问外部类的成员变量,只能访问外部类的静态成员变量。
外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问,必须先创建一个成员内部类的对象,通过指向这个对象的引用来访问。
内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。
- 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用。
- 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值。
- 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。
好处
静态内部类
- 只是为了降低包的深度,方便类的使用,静态内部类适用于包含类当中,但又不依赖与外在的类。
- 由于Java规定静态内部类不能用使用外在类的非静态属性和方法,所以只是为了方便管理类结构而定义。于是我们在创建静态内部类的时候,不需要外部类对象的引用
非静态内部类
- 内部类继承自某个类或实现某个接口,内部类的代码操作创建其他外围类的对象。所以你可以认为内部类提供了某种进入其外围类的窗口。
- 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
- 如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。(内部类有效地实现了”多重继承”)。
静态与非静态的区别
- 静态内部类不持有外部类的引用。
- 静态内部类不依赖外部类 。
- 普通内部类不能声明static的方法和变量。
- 非静态可以自由使用外部类所有的变量和方法。
内部类使用场景
- 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
- 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
- 方便编写事件驱动程序。
- 方便编写线程代码。
七.泛型
泛型的定义
泛型,即“参数化类型”,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
是一种未知得类型,当我们不知道使用什么数据类型得时候,可以使用泛型。
反编译出现泛型信息。
编译的特性:
- 只在编译阶段有效(在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段)。
- 在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
泛型的分类
- 泛型类(通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。)
- 泛型接口
- 泛型方法
八.代码块与代码加载顺序
局部代码块
- 位置:方法内部
- 作用:限定变量的生命周期,尽早释放,节约内存
- 调用:调用其所在方法时执行
构造代码块
- 位置:类成员的位置,就是类方法之外的位置
- 作用:把多个构造方法共同的部分提取出来,共用构造代码块
- 调用:每次调用构造方法时,都会优先于构造方法执行,每次new一个对象时自动调用
静态代码块
- 位置:类成员位置,用static修饰
- 作用:对类进行一些初始化,只加载一次,当new多个对象时,只有第一次会调用静态代码块
- 调用:new一个对象时自动调用,只调用一次
九.抽象类和接口
抽象类
一般会实现一部分操作,并且留一部分抽象方法让子类自己实现
- 变量:没有被public final修饰,只是一般成员变量
- 方法:可以有具体方法;抽象方法没有方法体,抽象方法不能被private修饰
接口
一般指一种规定,如果想实现一个具体的接口,接口中的方法就必须按照规定去实现,接口可以继承多个其他接口,但类智能单继承。
java8开始接口中的方法允许有方法体(静态方法和默认方法),java9可以使用私有静态方法。
- 变量:默认都为public final修饰,即常量,基本数据类型和引用都一样(大写下划线命名)。
- 方法:public abstract修饰,不允许有方法体;default修饰,允许有方法体;不允许加final
注意事项:
- 实现多个接口,存在重复的抽象方法,只需要覆盖重写一次;
- 如果实现类实现的多个接口当中,存在重复的默认方法,那么实现一定要对冲突默认方法进行重写;
- 一个类如果直接弗雷当中的方法,和接口当中的默认方法产生冲突,优先使用父类当中的方法;
十.Java三大特性
1.封装
用于访问权限控制,可以保护类中的信息,只提供想要被外界访问的信息。
访问范围 public>protected>package=default>private。
- public 包内外所有类可见
- protected 包内所有类可见,包外有继承关系的子类可见(子类对象可调用)
- default 表示默认,不仅本类访问,而且同包也可访问
- private 仅同一类中可见
2.继承
java只允许单继承,但可以通过内部类继承其他类来实现多继承
3.多态
程序在运行的过程中,同一种类型在不同的条件下表现不同的结果。多态也称为动态绑定,一般是在运行时刻才能确定方法的具体执行对象,这个过程也称为动态委派。顾名思义:一个对象拥有多种形态;
一般可分为两种,一个是重写overwrite,一个是重载override!
重写
由于继承关系中的子类有一个和父类同名同参数的方法,会覆盖掉父类方法。
又叫运行时多态,编译时看不出子类调用哪个方法,运行时操作数栈会先根据子类引用去子类的类信息中查找方法,找不到的话再到父类信息中查找方法。
重载
一个同名方法可以传入多个参数组合,同名方法如果参数相同,即使返回值不同也不能同时存在,编译会出错。
又叫编译时多态,编译期可以确定传入的参数组合,决定调用的具体方法是哪一个。
- 访问成员变量:通过对象名称访问,引用对象为谁,就优先访问谁;间接通过成员方法访问成员变量,方法属于谁优先谁,同上没有要向上找;
- 访问成员方法:new谁就用谁,否则就向上找;
- 好处:无论右边new换成哪个子对象,等号左边调用方法都不会改变;
方法重载优先级匹配
- 普通重载一般就是同名方法不同参数
- 当同名方法只有一个参数时,例如调用char参数方法,但没有char参数的方法
- 则按照调用顺序char -> int -> long -> double ->…, 当没有基本类型时,则自动装箱,调用包装类方法,若没有包装类方法,则调用包装类实现的接口的方法,最后调用持有多个参数的char …方法
编译期的静态分派与转型
JAVA基础就总结到这里了,后面会继续完善JAVA反射机制和JAVA集合的知识!如大家喜欢可以收藏~