今日任务
1、能够理解单例设计模式
2、能够独立使用继承
3、能够独立使用final关键字
1. 代码块
1.1.代码块的概念和格式
代码块,就是使用大括号括起来的一段代码;
书写格式:
{
代码;
}
1.2.代码块的分类
1.2.1. 静态代码块
静态代码块,就是使用static修饰的代码块,直接写在类中的代码块;
class Demo{
static{
System.out.pintln("静态代码块")
}
}
静态代码块的执行顺序:
注意:一般在开发中,静态代码块都要写在类的下面;
作用:
因为在类加载完成时,静态代码块已经执行结束,所以某些需要提前完成的工作,可以放在静态代码块中执行;
1.2.2. 构造代码块
构造代码块,也是直接写在类中的代码块;在构造代码块前面不需要任何的关键字修饰;
class Demo{
{
System.out.pintln("构造代码块")
}
}
构造代码块执行顺序:
应用:
原来凡是创建对象执行构造函数,都要执行构造代码块,所以如果某些操作在所有构造函数中都存在,那么可以提取到构造代码块中;
1.2.3. 局部代码块
局部代码块的写法和构造代码块一模一样;不同的时,局部代码块只能写在局部位置(函数中);
class Demo{
public static void main(String[] args){
{
System.out.pintln("局部代码块")
}
}
}
局部代码块的作用,就是用来限定部分代码的使用范围的;
2. 类中可以书写的成员
class Demo{
//静态成员变量
static int a = 10;
//非静态成员变量
int b =20;
//静态方法
static void show(){
System.out.pintln("静态方法")
}
//非静态方法
void func(){
System.out.pintln("非静态方法")
}
//构造方法
Demo(){
System.out.pintln("构造方法")
}
//静态代码块
static {
System.out.pintln("静态方法")
}
{
System.out.pintln("构造代码块")
}
}
3. 类加载和对象创建的过程
面试题:
class Demo {
int x;
int y = 3;
static int z = 10;
static {
System.out.println("z=" + z);
}
Demo() {
System.out.println("x=" + x);
System.out.println("y=" + y);
}
}
class DemoTest2 {
public static void main(String[] args) {
Demo d = new Demo();
}
}
3.1.类加载过程
1、JVM发现要使用一个类,首先要到方法区中找;如果找到了,就直接使用,如果没有找到,才会去找这个类的class文件,然后加载;
(在找class文件时,是根据classpath配置的地址去找;如果没有配置,就在当前目录找)
2、在硬盘上找到class文件后,就开始加载这个class,根据书写顺序,先后将静态成员加载到静态区域,非静态成员加载到非静态区域;
3、接下来为所有的静态成员变量分配变量空间,同时赋默认值;
4、接下来根据书写的顺序为静态成员变量显示赋值,同时执行静态代码块;
上面的步骤都执行完毕,类加载完成;
3.2.对象的创建过程
1、首先JVM在堆内存中开辟空间;
2、在对象空间中为类中的所有非静态成员变量分配空间,赋默认值;
3、调用相应的构造函数进栈;
4、在执行构造函数中的代码之前,先要执行隐式三步:
a) super():调用父类构造函数
b) 给对象空间中的非静态成员变量显示赋值
c) 执行构造代码块
5、完成隐式三步后,接下来开始执行构造函数中的代码;
构造函数结束出栈,对象创建完成;
3.3.练习
下列代码执行结果是什么?为什么?
代码一:
class Demo
{
static Demo demo = new Demo();
Demo(){
System.out.println("构造函数");
}
}
class Test
{
public static void main(String[] args)
{
new Demo();
}
}
代码二:
class Demo
{
Demo demo = new Demo();
Demo(){
System.out.println("构造函数");
}
}
class Test
{
public static void main(String[] args)
{
new Demo();
}
}
4. 单例设计模式
4.1.单例设计(singleton)模式介绍
4.1.1. 设计模式:
就是对一些常见问题进行归纳总结,并针对具体问题给出一套通用的解决办法(强调的是解决问题的思想);在开发中,只要遇到这类问题,就可以直接使用这些设计模式解决问题;
最早起源于建筑领域,在建筑领域把一些问题和经验进行归纳总结,形成一套可以用来在建筑领域解决大多数问题的方案;
后来计算机领域借鉴了建筑领域的设计模式,把计算机领域中经常遇到的问题进行归纳和总结,形成计算机领域23中设计模式;
4.1.2. 单例(单态、原子)设计模式:
在程序运行中,一个类最多只能有一个对象;
//需求:模拟月亮,不管哪里调用的月亮,都是同一个对象;
class Moon//描述月亮
{
/*
要创建对象,有两个条件:new关键字;构造函数;
要保证单例,就不能让别人随便创建对象;
在这两个条件中,new关键字程序员无法控制;
就只能考虑不让别人使用构造函数;
要想别人不能随意使用构造函数,就需要将构造函数私有化;
*/
private Moon(){}
/*
私有化构造函数,确实可以避免随意创建对象;
但是还是得有一个对象;
而构造函数私有化后在别的地方无法创建对象,
就只有在本类中创建这个唯一的对象;
创建的对象需要有一个变量接收,以后其他地方需要这个对象,
通过这个变量就可以获取这个对象了;
因为在使用这个变量之前还没有对象,所以这个变量必须是静态的;
为了保证数据的安全,不被外界修改,必须将他封装起来(也就是私有化)
*/
private static Moon moon = new Moon();
/*
要想外界还能访问到被封装的数据,必须向外提供一个公开的访问方法
而且只有访问这个方法之后才会有对象,所以这个方法应该是静态的
*/
public static Moon getMoon(){
return moon;
}
}
class Test
{
public static void main(String[] args)
{
Moon m1 = Moon.getMoon();
Moon m2 = Moon.getMoon();
System.out.println(m1);
System.out.println(m2);
System.out.println(m1 == m2);
}
}
4.2.单例设计模式的代码模板
总结实现单例的步骤:
1、私有化构造函数;
2、在本类中创建唯一实例对象;
3、对外提供公开的访问方法,获取这个对象
这种方式叫做饿汉式;
这种实现方式有点问题:
这种方式,只要使用到这个类,就一定会创建对象,会造成内存的浪费;
好处是:保证对象的唯一性;
解决办法:懒汉式
原理:
懒汉式的问题:
多线程环境下,不能保证每次获取的都是同一个对象;
好处:避免内存浪费;
4.3.单例设计总结
设计模式:针对某一类问题的通用的解决办法;
单例设计模式:解决程序运行中一个类最多只能有一个实例对象的问题;
单例实现的步骤:
1、私有构造函数,避免其他类可以直接创建单例类的对象;
2、在本类中创建唯一实例,使用静态成员变量保存;为保证安全性,私有化这个成员变量;
3、对外提供一个公开的静态方法,供其他类获取本类的唯一实例;
单例的两种实现方法:
饿汉式:在加载类的同时就创建了这个类的唯一实例;
好处:可保证这个类的实例的唯一性;
弊端:如果只是使用这个类,但是暂时不需要它的对象,也会创建唯一实例,造成内存的浪费;
懒汉式:在第一次调用获取实例的方法时才创建对象;
好处:第一次调用获取实例的方法时才创建对象,可以避免内存的浪费;
弊端:多线程环境下不能保证实例的唯一性;
5. 面向对象:继承
继承财产;
继承皇位;
继承传统;
继承都是发生在两类事物之间的,这两类事物都有关系,现实生活中是父子关系;
5.1.java中的继承
概念:
java中的继承,是使用extends关键字在两个类之间建立的一种关系;
写法:
class Fu{}
class Zi extends Fu{}//表示Zi类继承里Fu类;
在继承关系中,被其他类继承的类,叫做父类(超类),如本例中的Fu类;
继承其他类的类,叫做子类(派生类),如本例中的Zi类;
作用:
继承中子类可以直接拥有父类的成员;
继承演示:
案例:使用java代码描述人和学生的信息;
问题:
要解决这种应该是自身所有的属性和行为重复的问题,应该使用继承;
结论:使用继承可以提高代码的复用性;
使用继承可以在两个类中建立一种关系;
使用注意:
1、继承中,父类的私有成员可以被子类继承,但是不能直接被访问使用;
2、继承中的两个类,应该有关系;
只有子类描述的事物是 父类描述的事物的特例的时候,才可以使用继承;
虽然在语法上,可以使用extends关键字在任意两个类之间建立继承关系,但是在开发中,只能是二者之间具有“是” 的关系的时候才使用继承;
如果两个类不具有这种“是”的关系,那么就应该找他们共同的父类,然后将共同的信息放到共同的父类中,然后让两个类分别继承父类;
鱼和 苹果 , 不具有 “是” 的关系,但是有共同的父类,都属于食物,所以可以建立一个食物类,然后让他们分别继承食物类;
5.2.java类的继承特点
5.2.1. 单一继承
就是一个类只能直接继承一个父类;
如果可以继承两个父类,那么当这两个父类中都具有共同的属性或行为时,在子类中调用,就不清楚到底会调用哪个(调用的不确定性)
5.2.2. 多重继承
java中继承中,父类可以再继承其他类,叫做多重继承;
一个类只能直接继承一个父类,但是可以有多个子类;
一个类的父类还可以继承父类;
5.3.继承中的成员变量的特点
5.3.1. 子类直接拥有父类非私有成员变量
5.3.2. 子类中存在和父类中同名的成员变量,在子类中直接使用的是子类中定义的;
一般开发中,如果父类定义了某个成员变量,子类中一般不需要再定义;
5.4.继承中的成员方法
5.4.1. 子类直接拥有父类非私有成员方法
5.4.2. 子类中可以定义和父类中同样的成员方法,直接调用的是子类中定义的函数
结论:
如果子类中没有定义和父类中一样的成员变量和函数,直接调用,使用的是父类中定义的成员;
如果子类中定义了和父类中一样的成员变量和函数,直接调用,使用都是子类中定义的成员;
此时要使用父中定义的成员,需要通过super关键字调用;调用的格式是:
super.成员变量;
super.成员函数名(参数);
5.5.方法的重写(override)
5.5.1. 重写的概念
在子类中定义和父类中相同的函数,直接调用函数时,实际使用的是子类中的函数,这种情况叫做方法的重写(覆写);
一般开发中,如果父类的功能不满足子类需要,子类都会重写父类的函数;
5.5.2. 重写的应用
需求:描述手机这类事物。原始的手机和现代的手机
5.5.3. 重写的注意事项
1、子类重写的函数要和父类中的函数名要相同;
2、子类重写的函数要和父类中的函数参数列表要相同;
3、子类重写的函数要和父类中的函数返回值类型相同;
4、子类重写的函数要和父类中的函数的访问方式相同
(也就是说,父类的方法是静态的,重写的方法也必须是静态的;父类的方法不是静态的,重写的方法也必须不是静态的);
5、子类重写的函数,访问权限不能比父类的低;(可以和父类的访问权限不同,但是不能比父类访问权限低)
直接将父类中的函数复制粘贴到子类中,然后修改方法体的代码,可以保证不会出现格式上的问题;
5.6.继承中构造方法的使用
5.6.1. 子类实例化的过程
继承中类的加载顺序,是先加载父类,然后再加载子类;
1、为什么任何一个类(不包含Object)的构造函数中都需要一个super() 语句?
因为除了Object类以外,所有类都会继承一个父类;继承父类,那么子类实例化时就需要给父类中的成员变量显示赋值,就需要用到父类中的构造函数;
2、如果父类中没有无参构造函数,子类如何实例化?
super()表示调用父类无参构造函数;如果父类中没有无参构造函数,就会报错;
如何解决这个问题呢?
1、在父类中添加一个无参构造函数;
2、在子类的构造函数中显示的调用父类有参构造函数;
在子类中调用父类的构造函数,需要使用super关键字;格式是:super(参数);
在子类构造函数中使用super调用父类构造函数需要注意,这个super语句必须写在构造函数的第一行;
3、子类构造函数中,this() 和 super() 能否同时存在?
不能;因为他们都要写在构造函数的第一行;
所以如果一个构造函数中有this()语句,就没有super()语句,super()存在于this调用的那个构造函数里面;
4、如果一个类的构造函数全部私有了,还可以有子类吗?
不能;因为在子类的构造函数中一定要调用父类的构造函数;而一旦一个类的构造函数都私有了,就只能在本类中使用,其他类(也包括子类)都无法使用;
5.7.继承总结
继承的概念:通过extends关键字在两个类之间建立的一种关系;其中继承其他类的类叫做子类(派生类),被其他类继承的类叫做父类(超类);
继承关系,一般用来表示子类和父类的 是的关系,即子类描述的事物是 父类描述的事物的一个特例;
继承的格式:
class Fu{}//父类
class Zi **extends **Fu{}//子类
继承的作用:子类可以直接拥有父类成员;其中,私有成员和构造函数不参与继承;
java中类继承的特点:只支持单一继承和多重继承,不支持多继承(一个类不能同时继承多个类)
继承中成员变量的特点:
子类中可以直接使用父类中定义的非私有的成员变量;
如果子类中定义了和父类中相同的成员变量,直接调用,实际使用的是子类中定义的成员变量;要使用父类中定义的成员变量,需要使用关键字super,格式是:super.变量名;
继承中一般函数的特点:
子类中可以直接使用父类中定义的非私有的一般函数;
如果子类中定义了和父类中一样的函数,直接调用,实际使用的是子类定义的函数;要使用父类中定义的一般函数,需要使用关键字super,格式是:super.函数名(参数);
方法重写的概念:在继承中,如果子类中定义了和父类中一样的函数,则子类对象实际使用的是子类中定义的函数,这种情况叫做函数的重写;
子类重写父类函数需要注意的事项:
1、子类中重写的函数,函数名、参数列表、返回值类型和是否静态,必须和父类中函数相等;
2、子类中重写的函数,访问权限不能比父类中函数低;
继承中子类实例化的特点:
1、子类实例化时,实际只创建子类一个对象;
2、子类对象中会为父类中的非静态成员变量分配空间;
3、在执行子类的构造函数时,必须要先调用父类的构造函数,作用是给父类的成员变量显示赋值;
4、子类调用父类的构造函数,需要使用super关键字,格式是:super(参数);并且super语句必须在子类构造函数的第一行;
5、子类构造函数中调用其他构造函数的this语句不能和调用父类构造函数的super语句共存;
super小结:super,表示父类;作用是区分子类和父类的成员,以及在子类的构造函数中调用父类构造函数;
6. final关键字
6.1.final简介
final:表示最终的,最后的,主要用来修饰类、函数和变量;
final修饰类,直接写在class关键自前面,表示这个类不能被继承;
final修饰函数,直接写在函数的返回值类型前面,表示这个函数不能被重写,但是可以被继承;
final修饰变量,表示这个变量的值不能被修改;
6.2.final演示
6.2.1. 修饰类
6.2.2. 修饰函数
6.2.3. 修饰变量
因为被final修饰的变量的值不可改变,所以java中都使用它表示常量;
如果是常量,变量名的写法是:所有字母全部大写,多个单词之间使用下划线连接;
final int USER_AGE = 10;
被修饰的变量只是直接保存在变量中的值不能被修改,所以如果修饰的变量的数据类型是引用数据类型,那么这个引用不能修改,但是引用指向的对象中的数据可以修改;
6.3.final总结
final:最终的,最后的;可以修饰类、变量和函数;
修饰类,表示该类不可被继承;格式是直接写在class关键字前面;
修饰函数,表示继承这个类的子类中不能重写这个函数;格式是直接写在函数的返回值类型前面
修饰变量,表示该变量的值不可改变;格式是直接写在变量的数据类型前面;
注意:如果修饰的是引用类型的变量,则变量中保存的引用不可改变,但是引用指向的堆内存中的数据可以改变;