java基础面向对象知识点概要
面向对象与面向过程
面向对象与面向过程简介:
- 所谓面向过程就是以功能行为为主体,强调的是实现的功能行为,而面向对象是以对象为主体,强调的是每一个对象,将功能行为封装进每一个对象,再由对象去实现功能行为。
-
面向对象的三大特征:
①封装 (Encapsulation)
②继承 (Inheritance)
③多态 (Polymorphism)
Java基本元素:类和对象
类和对象:
- 类是对一类事物的描述,是抽象的,概念上的定义。
- 对象是实际存在的该类事物的每个个体,因为也被成为实例。
- 举例:如人就是一个类,有名字,年龄这些属性,吃饭,睡觉这些行为。而如詹姆斯就是一个具体的人,名字詹姆斯,年龄34,具有吃饭睡觉,打篮球这些行为。
- Java类及类的成员:Field = 属性 = 成员变量,Method = (成员)方法 = 函数
对象的创建和使用
创建对象语法: 类名 对象名 = new 类名();
匿名对象
匿名对象简述:
-
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。如:new Person().shout();
- 使用情况:如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
-
根据内存分析:匿名对象只在堆内存中开辟空间,而没有在栈内存的引用,因此可以减少内存消耗。
类的三大成员
类的成员(属性)简介:
- 属性也称为域,或字段。
- 语法格式:修饰符 数据类型 属性名 = 初始化值 ;
- 说明1(修饰符):常用的权限修饰符有:private、缺省、protected、public、static、final
- 说明2(数据类型):任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
-
说明3(属性名):属于标识符,符合命名规则和规范即可
类的成员(方法简介)(method、函数):
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码。
- Java里的方法不能独立存在,所有的方法必须定义在类里。
-
注 意:
①方法被调用一次,就会执行一次
②
没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
<1>
void就是一个基本类型,返回值为void也就是return;。
<2>
void关键字表示函数没有返回结果,是java中的一个关键字。
<3>
Void类是一个不可实例化的占位符类,用来保存一个引用代表了Java关键字void的Class对象。如果方法返回值是Void类型。那么该方法只能返回null类型(除了null不能返回其它类型)。
<3>
通过Void类的源代码可以看到,Void类型不可以继承与实例化。
③定义方法时,方法的结果应该返回给调用者,交由调用者处理。 方法中只能调用方法或属性,不可以在方法内部定义方法。
重载的概念:
- 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 重载的特点:与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
-
总结:重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
- 重载示例:
//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个小数的和
double add(double x,double y){return x+y;}
可变形参简述:
- JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
-
说明:
①声明格式:方法名(参数的类型名 …参数名)
②可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
③
可变个数形参的方法与同名的方法之间,彼此构成重载。
④
可变参数方法的使用与方法参数部分使用数组是一致的。
⑤
方法的参数部分有可变形参,需要放在形参声明的最后
⑥
在一个方法的形参位置,最多只能声明一个可变个数形参
方法参数的值传递机制:
-
方法,必须由其所在类或对象调用才有意义。若方法含有参数:
①形参:方法声明时的参数
②实参:方法调用时实际传给形参的参数值 -
Java的实参值如何传入方法呢?
①Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
②
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
③
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
递归方法:
- 递归方法包含了一种隐式的循环,它会重复地执行某段代码,但这个循环无须控制。
- 递归方法必须要向一个已知的方向递归,否则这种递归就变成了无穷递归,也就是循环里的死循环。
类的成员(构造器)简介:
-
构造器的特征:
①它具有与类相同的名称
②它不声明返回值类型。(与声明为void不同)
③不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
④构造器的作用:创建对象;给对象进行初始化 -
根据参数不同,构造器可以分为如下两类:
①隐式无参构造器(系统默认提供)
②显式定义一个或多个构造器(无参、有参) -
注 意:
①Java语言中,每个类都至少有一个构造器
②默认构造器的修饰符与所属类的修饰符一致
③一旦显式定义了构造器,则系统不再提供默认构造器
④一个类可以创建多个重载的构造器
⑤父类的构造器不可被子类继承
属性赋值过程:
-
赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值 -
赋值的先后顺序:
① – ② – ③ – ④
JavaBean:
- JavaBean是一种Java语言写成的可重用组件。
-
所谓javaBean,是指符合如下标准的Java类:
①类是公共的
②有一个无参的公共的构造器
③有属性,且有对应的get、set方法
OOP特征一:封装与隐藏
封装属性:
-
Java中通过将类的属性和方法进行私有化处理,可以将类的属性和方法进行封装,从而不被外界所直接引用,我们可以通过getter(),setter()方法来调用属性,从而:
①隐藏一个类中不需要对外提供的实现细节。
②使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作。
③便于修改,增强代码的可维护性。 -
Java权限修饰符public、protected、(缺省)、private置于类的成员定义前, 用来限定对象对该类成员的访问权限。
①对于class的权限修饰只可以用public和default(缺省):
<1>public类可以在任意地方被访问。
<2>default类只可以被同一个包内部的类访问。
②此外default(缺省)也可以叫做friendly。
封装方法:
- 例如当我们在一个类里定义了一个public类型的方法,但是这个方法由类中多个方法组成时,这时这些辅助方法是不能被外部所调用,它的唯一作用就是辅助public类型的方法,被调用也毫无意义。
- 这时我们可以将其声明为priviate类型。
单例模式(封装构造器):
- 当我们仅需要创建一个对象,而不需要创建多个对象时(如创建一个地球对象),为了防止创建出多个对象,需要封装构造器,并创建一个方法来调用构造器。
- 同时需要将该方法声明为static静态的,也就是本地方法,也可以成为类方法。该方法在该进入编译器时就存在了,不管实例化多少次这个类,这个方法内的方法体都只执行一次。
- 因此可以保证只构造一个对象!保证唯一性,从而达成单例模式!
封装类:
- 当该类不希望被其他包所引用时,可以将该包改成defalut(缺省)的。
关键字this:
-
在Java中,this关键字比较难理解,它的作用和其词义很接近。
①它在方法内部使用,即这个方法所属对象的引用。
②它在构造器内部使用,表示该构造器正在初始化的对象。 - this 可以调用类的属性、方法和构造器。
-
什么时候使用this关键字呢?
①当在方法内需要用到调用该方法的对象时,就用this。
②具体的:我们可以用this来区分属性和局部变量。
③比如:this.name = name; -
注意:
①可以在类的构造器中使用”this(形参列表)”的方式,调用本类中重载的其他的构造器!
②明确:构造器中不能通过”this(形参列表)“的方式调用自身构造器。如果一个类中声明了n个构造器,则最多有 n – 1个构造器中使用了”this(形参列表)”。 “this(形参列表)“必须声明在类的构造器的首行!在类的一个构造器中,最多只能声明一个”this(形参列表)”
关键字package、import:
-
package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:package顶层包名.子包名 ;
-
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。
①import语句告诉编译器到哪里去寻找类。
②语法格式:import 包名. 类名; -
注意:
①在源文件中使用import显式的导入指定包下的类或接口
②声明在包的声明和类的声明之间。
③如果需要导入多个类或接口,那么就并列显式多个import语句即可
④举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
⑤如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
⑥
如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
⑦如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
⑧import static组合的使用:调用指定类或接口下的静态的属性或方法
为什么使用带有Java import语句的通配符不好?
-
唯一的问题是它使你的本地命名空间变得混乱。
例如,假设您正在编写Swing应用程序,因此需要java.awt.Event,并且还与公司的日历系统连接,该系统具有com.mycompany.calendar.Event。如果使用通配符方法导入两者,则会发生以下三种情况之一:
①你java.awt.Event和之间有一个完全的命名冲突com.mycompany.calendar.Event,所以你甚至无法编译。
②你实际上只管理导入一个(只有你的两个导入中的一个.*),但它是错误的,你很难找出你的代码声称类型错误的原因。
③当您编译代码com.mycompany.calendar.Event时没有,但是当他们稍后添加一个您之前有效的代码时突然停止编译。 - 显式列出所有导入的优点是,我可以一目了然地告诉您要使用哪个类,这使得阅读代码变得更加容易。如果你只是做一个快速的一次性事情,没有明显的错误,但未来的维护者将感谢你的清晰度。
-
IDEA设置方法:Settings -> Editor -> Code Style -> Java -> Imports 两个count to use import with '*' 设置成999。
OOP特征二:继承性
继承性简介:
- 继承性 即我们可以创建一个人 的类 然后一个爸爸对象实现了这个类,接着一个儿子对象也实现了这个类,而如果这个爸爸类的属性和方法在儿子类中都有,并且儿子也自己扩展了自己的一些属性和方法。那么可以说儿子继承了爸爸。
-
为什么要有继承?
①多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
②此处的多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类” - 类继承语法规则:
class Subclass extends SuperClass{ }
-
作用:
①继承的出现减少了代码冗余,提高了代码的复用性。
②继承的出现,更有利于功能的扩展。
③继承的出现让类与类之间产生了关系,提供了多态的前提。 -
注意:
①
此外Java只支持单继承和多层继承,不支持多继承。即一个子类只能继承一个父类,一个父类可以有多个子类,且一个子类可以有多个间接父类(即通俗意义上的爷爷,祖爷)。
方法的重写概念:
-
子类可以对父类中继承的方法进行重写,重写的规定是:
①形参的数据类型和个数都不能修改。
②
返回类型可以修改,但返回的类型不能大于父类的返回类型。
③访问权限修饰符可以修改,且只能比父类的权限大,但父类中的private方法不能重写。
④抛出的异常也可以修改,但抛出的异常不能大于父类抛出的异常 -
总结:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
四种访问权限修饰符简介:
-
Java默认访问权限没有任何关键字,但通常是指包访问权限(有时也表示成为friendly)。
关键字super简介:
- super关键字与this关键字类似,this代表当前对象或当前正在创建的对象,而sper代表父类对象。其用法规则和规范也与this类似。
-
在Java类中使用super来调用父类中的指定操作:
①super可用于访问父类中定义的属性
②super可用于调用父类中定义的成员方法
③super可用于在子类构造器中调用父类的构造器 -
注意:
①尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
②super的追溯不仅限于直接父类
③super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识 -
此外:
①子类中所有的构造器默认都会访问父类中空参数的构造器
②当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
③如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
子类对象实例化过程简介:
-
重点在于:如果子类中没有显示地调用父类构造方法,系统会默认调用父类的无参构造方法,直到追溯到Object类为止。
OOP特征三:多态性
多态性简介:
- 多态性是指父类的引用可以指向子类的对象。
- 虚拟方法调用是指当调用一个变量的方法时,编译时规定只能调用父类引用的有的方法,但是在运行时实际上调用的是子类的重写父类的方法。
- 多态性,是面向对象中最重要的概念。
多态性在Java中的体现:
-
对象的多态性:父类的引用指向子类的对象
①可以直接应用在抽象类和接口上 -
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明 该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
①
简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
-
多态情况下:
①“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
②“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法) -
对象的多态:在Java中,子类的对象可以替代父类的对象使用
①一个变量只能有一种确定的数据类型
②一个引用类型变量可能指向(引用)多种不同类型的对象
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
-
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
注意:
-
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
①
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
-
方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
- 正常的方法调用:
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
-
虚拟方法调用(多态情况下) 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,
父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
-
编译时类型和运行时类型:
①
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
-
子类继承父类
①若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
②
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
重载和重写的区别:
- 定义细节上:重载是规定形参的个数和数据类型必须不同,而重写是规定形参的个数和数据类型必须相同。
-
从编译和运行的角度看:
①重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
②所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,
这称为“晚绑定”或“动态绑定”。
③引用一句Bruce Eckel的话:
“不要犯傻,如果它不是晚绑定,它就不是多态。”
多态小结:
- 多态作用:提高了代码的通用性,常称作接口重用
-
前提:
①
需要存在继承或者实现关系
②
有方法的重写
-
成员方法:
①编译时:要查看引用变量所声明的类中是否有所调用的方法。
②运行时:调用实际new的对象所属的类中的重写方法。 - 成员变量:不具备多态性,只看引用变量所声明的类
instanceof 操作符:
-
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
①要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
②如果x属于类A的子类B,x instanceof A值也为true。
对象类型转换 (Casting ):
-
基本数据类型的Casting
①自动类型转换:小的数据类型可以自动转换成大的数据类型
<1>如long g=20;
<2>double d=12.0f
②
强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
<1>如float f=(float)12.0;
<2>int a=(int)1200L -
对Java对象的强制类型转换称为造型:
①从子类到父类的类型转换可以自动进行
②
从父类到子类的类型转换必须通过造型(强制类型转换)实现
③无继承关系的引用类型间的转换是非法的
④在造型前可以使用instanceof操作符测试一个对象的类型
总结:
Object类的使用
简介:
- Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
public class Person {
...
}
等价于:
public class Person extends Object {
...
}
Object主要结构:
- protected Object clone() 创建并返回此对象的一个副本。
- boolean equals(Object obj) 指示某个其他对象是否与此对象“相等”。
- protected void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
- Class<? extendsObject> getClass() 返回一个对象的运行时类。
- int hashCode() 返回该对象的哈希码值。
- void notify() 唤醒在此对象监视器上等待的单个线程。
- void notifyAll() 唤醒在此对象监视器上等待的所有线程。
-
String toString() 返回该对象的字符串表示,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。
它的值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())
。 - void wait() 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
- void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或
- notifyAll() 方法,或者超过指定的时间量。
- void wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify()
- Object类中equals()方法比较的是地址问题,toString()方法打印的是地址。 因此equals()方法与”==”没什么不同。
- 之所以String,file,包装类,时间类可以比较内容,是因为他们重写了equals()和toString()方法。
深拷贝(clone)、浅拷贝:
-
简易理解:
①浅拷贝:只拷贝源对象的地址,所以源对象的任何值变化,拷贝对象值也随着变化。
②深拷贝:拷贝对象的值,而不是地址,当源对象的值发生变化,拷贝对象的值不会发生变化 -
为啥我们需要深拷贝呢?
①在某些业务场景下,我们需要完全相同但是缺不相关联的对象。
②其实大体有两种,Serializable与Cloneable -
深拷贝的几种方式:
①构造函数方式
②重写clone方法
③序列化:
<1>Apache Commons Lang 序列化
<2>Gson序列化
<3>Jackson序列化. -
链接:
深拷贝(clone)、浅拷贝
包装类的使用
简介:
-
在有些时候我们需要将基本数据类型当成引用数据类型传入方法形参时 这时我们可以将基本数据类型包装成引用数据类型。
-
互相转换的方法:
-
定义变量的时候为什么用的是Integer而不是int?
①
Integer 允许为null值,int默认0,数据库里面如果有个字段没有值可能默认值为null,用Integer比较合适。
②
如果使用int,默认为0这样会产生歧义。
③
其他包装类同理。
-
何时发生自动装箱和自动拆箱?
①
赋值时:
在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。
②
方法调用时:
当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。
-------------------------------赋值时--------------------------------
//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()
//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
--------------------------------方法调用时-------------------------------
public static Integer show(Integer iParam){
System.out.println("autoboxing example - method invocation i: " + iParam);
return iParam;
}
//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer
show方法接受Integer对象作为参数,当调用show(3)时,会将int值转换成对应的Integer对象,
这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);中result为int类型,
所以这时候发生自动拆箱操作,将show方法的返回的Integer对象转换成int值。
注意
:
- Integer包装类的底层有一个内部类里面定义了Integer[]数据,里面保存了-128到127范围的整数,如果我们使用自动装箱的方式给Integer赋值的范围在-128 到127之间,我们可以直接从数组里取,而不用去new()。
-
有一个规范叫JSL(Java Language Specification,java语言规范)对Integer的缓冲做了约束,规定其范围为:(-128-127)之间,如下JDK源码:
-
如果超出了范围,会从堆区new一个Integer对象来存放值。
①其实有上图第二行代码:static final int low = -128;规定了下线为-128,但是最大范围没有确定下来,这个设计是方便优化或扩展JVM来预留的。
②固定了缓冲的下限,但是上限可以通过设置JDK的AutoBoxCacheMax参数调整。如下JDK源码中的注释也给出了相关说明:
-
此外:
①
Integer为null赋值给int变量会报java.lang.NullPointerException。
②
其实是因为Interger类型值为null,默认调用了自己的自动拆箱方法null.intValue(),转为int。
关键字:static
static简述:
- 关键字static表示静态,可以修饰变量,方法,不能修饰类,但有一个例外可以修饰内部类。
- 当static修饰变量及方法时表明这些变量和方法会随着类的加载而加载,在内存空间中类存在唯一的一个类,因此这些变量和方法也在内存中存在唯一的个。
- 类中的所有实例都可以共享这些方法和变量。
- 他们都放在内存中的方法区。
- 因此变量也可称为类变量。方法也可称为类方法。
- 我们还可以用类名.变量 或 类名.方法的形式去调用。当然也可以用对象去调用。但直接用类名调用不仅 不用消耗内存去创建对象,也方便简洁。
-
当static的变量未赋初始值时,会默认给它个初始值(和成员变量一样)。
单例模式(懒汉和饿汉)简述:
- 所谓单例模式表示只能够创建一个该类的对象。因此我们可以用需要将类的构造器私有化,然后让方法去调用。但是我们不能创建对象了,怎么去调用方法呢?这时我们可以将方法设置为静态static的,通过类名去调用。
-
懒汉模式:
①懒汉模式表示 我们在用到该方法时才去创建对象,当我们二次调用时,该方法会判断是否存在对象,若已存在,则返回该对象。
②
因此叫懒汉,用到才去创建
-
饿汉模式:
①饿汉模式表示 我们在类中创建好对象,该方法直接去调用。
②
因此叫饿汉,还没用,就已经迫不及待地创建了。
理解main方法的语法
简述:
- main 方法 具体全名 public static void main(String[] args)
- 由于虚拟机需要调用main()方法,因此他的权限必须是public的,
- 又因为虚拟机在调用时不必创建对象,因此该方法必须是static的。
类的成员之四:代码块
代码块简介:
-
类的成员除了属性,构造器,方法外,还有一个叫代码块。但代码块在实际开发中不常用。
-
静态代码块:用static 修饰的代码块:
①可以有输出语句。
②可以对类的属性、类的声明进行初始化操作。
③不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
④若有多个静态的代码块,那么按照从上到下的顺序依次执行。
⑤静态代码块的执行要先于非静态代码块。
⑥
静态代码块随着类的加载而加载,且只执行一次。
-
非静态代码块:没有static修饰的代码块:
①可以有输出语句。
②可以对类的属性、类的声明进行初始化操作。
③除了调用非静态的结构外,还可以调用静态的变量或方法。
④若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
⑤
每次创建对象的时候,都会执行一次。且先于构造器执行。
关键字:final
final简介:
-
final关键词表示不可变的,最终的。
①当修饰变量时,表示该变量是不可改变的,也就是常量。
②当修饰方法时,表示该方法是最终的,也就是该方法不可以重写。
③当修饰类时,表示该类不可以被继承。也就是没有子类。
④final修饰构造器和代码块都没有意义。 -
原理:
①
通过final修饰来禁止cpu的指令重排,来提供线程的可见性,来保证对象的安全发布,防止对象引用被其它线程在对象被完全构造完成之前拿到并使用。
②编译器和处理器要遵守两个重排序规则:
<1>在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
<2>初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
抽象类与抽象方法
抽象类与抽象方法:
- 抽象方法表示该方法时抽象的,即没有具体内容的,也就是没有方法体。
- 抽象类是不可以被实例化的。
- 当一个类有抽象方法时,该类必须是抽象类。因为如果该类被实例化的话。那么抽象方法被调用时将没有方法体可用。
- 反之一个抽象类可以没有抽象方法。
- 抽象类与其他类一样可以声明属性,构造器。
-
抽象类的构造器虽然不能在外部调用使用,但可以通过继承的子类的里的继承方法链调用。
- 抽象类的作用是当些方法的功能不具体时,需要根据不同子类去实现不同方法时,这时可以声明为抽象方法不去实现方法体。
-
总结:抽象类不能直接通过new去实例化一个对象,那它就是不能实例化,要获取抽象类的对象, 需要先用一个类继承抽象类,然后去实例化子类。也可以用匿名内部类,在抽象类中创建一个匿名的子类,继承抽象类,通过特殊的语法实例化子类的对象 。
-
为什么抽象类不能实例化对象?
①
因为抽象类是存在抽象方法的,如果能让抽象类创建对象的话,那么使用抽象类的对象调用抽象方法是没有任何意义的。
模版设计模式:
- 模版设计模式 只要理解了抽象的概念 相对来说比较简单。就是声明一个抽象类,实现某些方法,抽象一些方法。由调用者去实现。
接口(interface)
接口概述:
- 接口与抽象类一样。方法都是抽象的。
-
但不同的是接口只能声明静态常量,静态方法,缺省方法和抽象方法。
-
同时接口可以被实现(
类只能继承一个类,可以实现多个接口
)。 -
接口不能实现接口,只能继承接口,而且能多继承。
①
Java中为什么接口可以继承接口,但是接口不能实现接口呢?
因为接口是用来声明方法的,而不能写具体的实现,也就是没有方法体。然而实现一个接口,必须重写其声明的所有方法。
②
为什么java不能多继承但是可以实现多个接口?(接口多继承接口同理)
因为java只支持单继承,这是由于安全性的考虑,如果子类继承的多个父类里面有相同的方法或者属性,子类将不知道具体要继承哪个,而接口可以多实现,是因为接口只定义方法,而没有具体的逻辑实现,多实现也要重新实现方法。
- 因此接口表示的是功能。例如人是一个类,小明继承该类是个对象,但可以有许多功能,如吃饭接口,睡觉接口。
在jdk8之前:
-
interface之中可以定义变量和方法,
变量必须是public、static、final的
,
方法必须是public、abstract的
。 - 由于这些修饰符都是默认的,所以在JDK8之前,下面的写法都是等价的。
public interface JDK8BeforeInterface {
public static final int field1 = 0;
int field2 = 0;
public abstract void method1(int a) throws Exception;
void method2(int a) throws Exception;
}
jdk8的改进:
- 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
- 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:java 8API中对Collection、List、Comparator等接口提供了丰富的默认方法。
-
为什么要出现默认方法:
①当一个接口添加新方法时,需要所有的实现类都重写新方法,影响到了已有的实现类,可能导致应用崩溃;因此为了避免这种问题的出现我们可以使用默认方法来解决。
public interface JDK8Interface {
// static修饰符定义静态方法
static void staticMethod() {
System.out.println("接口中的静态方法");
}
// default修饰符定义默认方法
default void defaultMethod() {
System.out.println("接口中的默认方法");
}
}
接口的规定:
代理模式:
- 代理模式意思就是 代理某个对象,然后通过这个代理对象去实现该对象的方法,此外还可以加入额外的方法。
- 而代理对象是通过实现和被代理对象同样的接口从而实现同样的方法,同时把被代理对象传入,去重写实现的接口的方法,在实现被代理对象的方法的同时可以额外实现其他方法。
类的成员之五:内部类
简述:
- 内部类就是在一个类的内部再声明多一个类。
-
内部类总体分为两种:
①
局部内部类
②
成员内部类
局部内部类:
- 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private。
- 局部内部类不能使用static修饰,因此也不能包含静态成员。
-
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
-
局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
-
局部内部类我们可以这样分类:
局部匿名内部类和局部非匿名内部类。
成员内部类:
- 成员内部类和其他类的成员一样可以被static声明为静态的,final不可继承的,abstract抽象的,但我们一般把成员内部类主要分为两种静态和非静态。
- 静态内部类只可以调用外部类的静态的方法或属性,而非静态则都可以调用。
- 内部类不同于外部类只可以被 public 和 default修饰,还可以被protected和private修饰。
-
成员内部类
可以直接使用外部类的所有成员包括私有的数据
,语法为
外部类.this.变量
,
内部类访问自己
,语法为
this.变量
-
外部类也可以直接使用内部类的所有成员:
①Java语言规范里只说了enclosing class可以访问inner class的private/protected成员,inner class也可以访问enclosing class的private/protected成员,但是没有规定死要如何实现这种访问。
②JVM规范则在大多数时候都把每个Class都看作等价于top-level的,也就是说不关心enclosing / inner class之间的嵌套关系。对JVM来说,enclosing class和inner class在大部分情况下都是“不相关的两个类”,所以它们之间相互是不能访问对方的private/protected成员的。
③在实现中,衔接Java语言规范与JVM规范的就是Java源码级编译器(例如javac、ECJ等)。既然规范没规定死要如何实现,各个编译器都可以自己发明自己的办法。下面就说说javac的做法。
④
所以简单来说就是在enclosing / inner class之间要访问对方的private/protected成员时,javac会生成合适的“access method”(上面的access$xxx形式的方法)来提供合适的可访问性,这样就绕开了原本的成员的可访问性不足的问题。
⑤但这种Java语言与JVM之间的不匹配有时候会让一些程序要用非常蛋疼的代码来实现,特别是需要做动态字节码生成的时候。所以Java核心开发组也有在考虑在未来的Java版本中让JVM能更主动的识别类之间的嵌套关系,并直接在JVM层面上让enclosing / inner class之间有合适的可访问性,这样就不用再依赖Java源码级编译器的magic了。
匿名内部类
匿名内部类的特点:
-
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
- 格式:
new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}
-
如果要执行的任务需要一个对象,但却不值得创建全新的对象(原因可能是所需的类过于简单,或者是由于它只在一个方法内部使用),匿名类就显得非常有用。
①例如我们需要一个监听类,并且在实例化这个监听类的时候才需要实现这个监听类内部的方法,那么使用匿名类就最方便了。