重点理解抽象类和接口的联系与区别:
抽象类主要作为多各类的模板 接口定义了多类应该遵守的规范
一.Java8增强的包装类
1.Integer包装类
Integer a = 2;
Integer b = 2;
System.out.println(a == b);//true
Integer aa = 128;
Integer bb = 128;
System.out.println(aa == bb);//false
注意:系统把-128~127之间的整数自动装箱成Integer实例,并放入了一个名为cache的数组中缓存起来;如果以后把一个-128~127之间的整数自动装箱成一个Integer实例,实际上直接指向对应的数组元素,永远都是引用cache数组的同一个数组元素,所以相等;
二.处理对象
1.打印对象和toString()方法
Object类提供的toString()方法:"类名+@+hashCode",所以一般要重写这个方法;
2.==和equals()方法
==:
a.如果两个变量是基本类型,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就返回true;
b.如果是两个引用类型变量,只有他们指向同一个对象时,==才返回true;
重点:"hello"直接量和new String("hello")区别?
当Java程序直接使用"hello"字符串直接量时(包括可以再编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;
当使用new String("hello")是,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中,换句话说,new String("hello")一个产生了两个字符串对象!
equals():
equals()方法是Object类提供的一个实例方法,判断两个对象相等的标准与==运算符没有区别;所以意义不大,最好采用重写equals()方法,String类就重写了equals()方法;
三.类成员
static关键字修饰的成员就是类成员,有类变量,类方法,静态初始化块,static关键字不能修饰构造器.static修饰的类成员属于整个类,不属于单个实例;
1.理解类成员
a.Java类里可以包含的5种成员:变量 方法 构造器 初始化块 内部类(包括接口,枚举类);
b.对static关键字而言,有一条非常重要的规则:
类成员不能访问实例成员,因为类成员属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况,如果允许类成员访问实例成员将会引起大量的错误!
2.单例类(Singleton)
如果一个类始终只能创建一个实例,则这个类被称为单例类:
根据良好封装的原则:
一旦把该类的构造器隐藏起来,就要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用改方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类);
除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾创建过改对象,也就无法保证只创建一个对象;所以需要提供一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,所以该成员变量必须使用static修饰;
四.final修饰符
final关键字可用于修饰类,变量和方法;
final修饰变量时,表示该变量一旦获得了初始值就不可被改变,final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量,形参;
严格的说,final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值;
1.final成员变量
a.成员变量是随类初始化或对象初始化而初始化的.当类初始化时,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值;
b.对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值;
c.final修饰的成员变量必须由程序显式的指定初始值;
总结:final修饰的类变量,实例变量能指定初始值的地方如下:
I:类变量:1️⃣静态块 2️⃣声明时
II:实例变量:1️⃣普通快 2️⃣声明时 3️⃣构造器
注意点1:
如果普通初始化块已经为某个实例变量指定了初始值,则不能再在构造器中为改实例变量指定初始值;
final修饰的类变量,要么在定义该类变量时指定初始值,要么在静态初始化块中为该类变量指定初始值;
注意点2:
与普通成员变量不同的是,final成员变量(包括实例变量和类变量)必须由程序员显式初始化,系统不会对final成员进行隐式初始化;
2.final局部变量
a.系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化;
b.如果final修饰的局部变量在定义时没有指定默认值,则可以再后面代码中对该变量赋初始值,但只能一次,不能重复赋值;如果final修饰的局部变量在定义时已经指定默认值,则后面代码中不能再对该变量赋值;
c.不能对final修饰的形参赋值;
final修饰局部变量 --> 修饰形参:不能赋值
非形参:若声明时赋值了,则以后不能赋值
若声明时未赋值,则只能赋值一次
3.final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变;
当使用final修饰引用了诶西变量时,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变;
4.可执行"宏替换"的final变量
满足一下三个条件,这个final变量就不是一个变量,相当于一个直接量:
1️⃣.使用final修饰符修饰;
2️⃣.在定义该final变量时指定了初始值;
3️⃣.该初始值可以再编译时就被确定下来;
//定义一个普通局部变量
final int a = 5;
System.out.println(a);
对于这个程序来说,变量a其实不存在,当程序执行System.out.println(a);时,实际转换为执行System.out.println(5);
总结:
a.当定义final变量时就为该变量指定了初始值,而且该初始值可以再编译时就确定下来那么这个final变量本质上就是一个"宏变量",编译器会把程序中所有用到该变量的地方直接替换成该变量的值;
b.除了为final变量赋值赋直接量的情况外,如果被赋的表达式知识基本的算术表达式或字符串连接运算符,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成"宏变量"处理;
c.Java会使用常量池来管理曾经用过的字符串直接量,String a = "java";执行这个语句后,常量池就会缓存一个字符串"java";如果程序再次执行String b = "java',系统将会让b直接指向常量池中的"java"字符串,因此a == b返回true;
String s1 = "疯狂Java";
//s2变量引用的字符串可以再编译时就确定下来
//因此s2直接引用常量池中已有的"疯狂Java"字符串
String s2 = "疯狂" + "Java";
System.out.println(s2 == s2);//true
//定义2个字符串直接量
String str1 = "疯狂";
String str2 = "Java";
String str3 = str1 + str2;
System.out.println(s1 == s3);//false
对于s3而言,由str1和str2进行连接运算后得到.由于str1,str2是两个普通变量,编译器不会执行"宏替换",因此编译器无法在编译时就确定s3的值,也就无法让s3指向字符串池中缓存的"疯狂Java" --> s1 == s3 为false;
让s1 == s3 为true的话,只要让编译器对str1,str2执行宏替换,这编译器就可以再编译阶段确定s3的值,即对str1,str2用final进行修饰;
d.对于final实例变量而言,只有在定义该变量时指定初始值才有"宏替换"的效果;
5.final方法
a.final修饰的方法不可被重写,如果不希望子类重写父类的某个方法,可以使用final修饰该方法;
b.对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法-----如果子类中定义了一个与父类private方法有相同方法名,相同形参列表,相同返回值类型的方法,也不是方法重写------只是重新定义了一个新方法!
c.final修饰的方法仅仅是不能被重写,完全可以被重载;
6.final类
a.final修饰的类不可以有子类,即final修饰的类不可被继承;
7.不可变类
a.定义:
不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的;
如果要创建自定义的不可变类可遵守如下规则:
→使用private和final修饰符来修饰该类的成员变量
→提供带参构造器,来初始化成员变量
→仅仅提供getter方法
→如果有必要,重写Object类的hashCode(),equals()方法;
b.与不可变类对应的是可变类,可变类的含义是该类的实例变量是可变的;
8.缓存实例的不可变类
如果某个对象需要频繁的重复使用,缓存该实例就利大于弊,比如Integer就缓存了-128~127的Integer对象;
五.抽象类
定义一个Shape类,这个类提供计算周长calPerimeter()方法,但是不同的子类对周长的计算方法不一样,该如何处理?
方案1:既然Shape类不知道如何实现calPerimeter()方法,就不要管了,这不行,加入有一个Shape引用变量,该变量实际引用Shape子类的实例,那么这个Shape变量就无法调用calPerimeter()方法,必须将其强制类型转换为其子类类型,才可调用calPerimeter()方法,不灵活;
方案2:通过定义抽象方法;
1.抽象方法和抽象类
a.抽象方法和抽象类必须使用abstract修饰来定义,有抽象方法的类只能被定义为抽象类,抽象类可以没有抽象方法;
抽象方法,抽象类的规则:
→抽象类,抽象方法必须使用abstract来修饰,抽象方法不能有方法体;
→抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例;
→抽象类可以包含成员变量,方法(普通方法和抽象方法都可以),构造器,初始化块,内部类(接口,枚举);
→含有抽象方法的类,或实现了一个接口,但没有完全实现接口包含的抽象方法的类,只能被定义为抽象类;
b.利用抽象类和抽象方法的优势,可以更好的发挥多态的优势;
当使用abstract修饰类时,表明这个类只能被继承;当使用abstract修饰方法时,表面这个方法必须由子类提供实现(重写),而final修饰的类不能被继承,修饰的方法不能被重写,因此final和abstract永远不能同时使用;
c.static和abstract不能同时修饰方法,可以同时修饰内部类;
d.private和abstract不能同时修饰方法;
2.抽象类的作用
a.从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,避免子类设计的随意性
b.提供一个抽象类,父类提供了多个子类的通用方法,并把一个或多个方法留给子类实现,局势模板模式;
六.Java8改进的接口
抽象类是从多个类抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的"抽象类"-----接口.接口不能包含普通方法,所有的方法都是抽象方法.Java8对接口进行了改造,允许在接口中定义默认方法,默认方法可以提供方法实现;
1.接口的概念
a.接口是从多个相似类中抽象出来的规范,接口不提供任何实现,因此,接口定义的是多个类共同的公共行为规范;
2.Java8中接口的定义
a.由于接口定义的是一种规范,因此接口不能包含构造器和初始化块,接口里面可以包含成员变量(只能是静态常量),方法(抽象实例方法,类方法,默认方法),内部类(内部接口,枚举);
b.因为接口里定义的是多个类共同的公共行为规范,因此接口里的所有成员,包括常量,方法,内部类和内部枚举都是public访问权限;
c.不管是否使用public static final修饰符,接口里的成员变量总是使用者三个修饰符来修饰;
//系统自动为接口里定义的成员变量增加public static final 修饰符
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;
//上面两行代码的结果完全一样
d.修饰符:
成员变量:public static final
方法:public abstract public static public default
//默认增加public abstract
void getData();
//默认方法,需要增加default修饰,并且默认增加public
default void print(String str){
System.out.println("111");
}
//类方法,需要增加static修饰,冰清默认增加public
static String test(){
System.out.println("2");
}
由于默认方法并没有static修饰,因此不能直接使用接口来调用默认方法,需要使用接口的实现类的实例来调用默认方法;
类方法可以直接使用接口来调用;
3.接口的继承
接口的继承和类继承不一样,接口完全支持多继承,即一个接口可以由多个直接父接口;
4.使用接口
作用:
→定义变量,也可用于进行强制类型转换;
→调用接口中定义的常量;
→被其他类实现;
实现接口与继承父类相似,一样可以获得所实现接口里定义的常量(成员变量),方法(包括抽象方法和默认方法);
5.接口和抽象类
接口:作为系统与外界交互的窗口,接口体现的是一种规范;
抽象类:抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计;
6.面向接口编程