Java温习——面向对象第四部分

来源:互联网 发布:mac os vmware 镜像 编辑:程序博客网 时间:2024/05/01 05:27

一、代码块

1 概念

在类或方法中,直接使用“{}”括起来的一段代码,表示一段代码区域;

代码块里的变量是局部变量,只在自己所在区域内有效;


2 分类

根据代码块定义的位置不同,分为三种形式:

(1)局部代码块

直接定义在方法内部的代码块;

class CodeBlockDemo{public static void main(String[] args){// 局部代码块{int i = 1;System.out.println(i);}// System.out.println(i); // 编译报错}}


注:

一般不直接使用局部代码块,而是结合if、while、for、try、catch等关键字使用,仍代表一段代码区域;


(2)初始化代码块(又构造代码块)

直接定义在类中;

每次创建对象都会执行初始化代码块;

每次创建对象都会先调用本类中的初始化代码块,再调用构造器;

/*进入主方法初始化代码块构造器初始化代码块构造器初始化代码块构造器*/class CodeBlockDemo{{System.out.println("初始化代码块");}CodeBlockDemo(){System.out.println("构造器");}public static void main(String[] args){System.out.println("进入主方法");// 创建3个CodeBlockDemo对象new CodeBlockDemo();new CodeBlockDemo();new CodeBlockDemo();}}


通过反编译,发现初始化代码块也作为构造器的最初语句;



一般不使用初始化代码块,因为一般初始化操作在构造器中完成,若初始化操作较多,专门定义一个方法作初始化操作,在构造器中调用即可;


(3)静态代码块

使用static修饰的初始化代码块;

在main方法执行之前执行静态代码块,且只执行一次;

main方法是程序的入口,因为静态成员随字节码的加载也加载到JVM,此时main方法还未执行,因为方法需要JVM调用;

先把字节码加载到JVM,再由JVM调用main方法;

用以对类作初始化操作,加载资源(配置文件等);


如下,确定代码执行顺序:

class SuperClass{SuperClass(){System.out.println("SuperClass构造器");}}class SubClass extends SuperClass{static{System.out.println("SubClass静态代码块");}SubClass(){System.out.println("SubClass构造器");}}class ExeSeqDemo{private static ExeSeqDemo d = new ExeSeqDemo();// 编译前// ---------------------------------------------------private SubClass t = new SubClass();{System.out.println("ExeSeqDemo初始化代码块");}public ExeSeqDemo(){System.out.println("ExeSeqDemo构造器");}// ---------------------------------------------------static{System.out.println("ExeSeqDemo静态代码块");}public static void main(String[] args){System.out.println("进入主方法");new ExeSeqDemo();}}


执行结果如下:

SubClass静态代码块
SuperClass构造器
SubClass构造器
ExeSeqDemo初始化代码块
ExeSeqDemo构造器
ExeSeqDemo静态代码块
进入主方法
SuperClass构造器
SubClass构造器
ExeSeqDemo初始化代码块
ExeSeqDemo构造器


原理分析:

JVM首先寻找带有main方法的类ExeSeqDemo,且必须保证该类所依赖的类(字段中——自身和SubClass)先加载到JVM中,因此先加载SubClass类,再加载ExeSeqDemo类,但SubClass类是SuperClass类的子类,应该先加载父类,再加载子类,所以JVM加载类的顺序为SuperClass -> SubClass -> ExeSeqDemo;

JVM依次加载类,其实就是执行所在类中static语句块(static成员变量和static代码块的执行顺序按照自上向下的顺序依次执行),即执行System.out.println("SubClass静态代码块");语句和private static ExeSeqDemo d = new ExeSeqDemo();语句,而执行第二条static语句其实是创建ExeSeqDemo对象,首先执行ExeSeqDemo构造器,而上述代码在编译后的执行顺序如下,所有非staitc字段的初始化都位于构造器中,且优先执行,所有static字段的初始化都位于static代码块,且优先执行

private SubClass t = null;public ExeSeqDemo(){t = new SubClass();System.out.println("ExeSeqDemo初始化代码块");System.out.println("ExeSeqDemo构造器");}


因此,需要先创建SubClass对象,执行SubClass类构造器,但该类继承SuperClass类,应该先执行父类构造器,再执行子类构造器,因此先执行SuperClass构造器,再执行SubClass构造器,即执行System.out.println("SuperClass构造器");和System.out.println("SubClass构造器");语句,然后顺序执行ExeSeqDemo构造器中其他语句—— System.out.println("ExeSeqDemo初始化代码块");和System.out.println("ExeSeqDemo构造器");语句;

执行完static成员变量后执行static代码块 —— System.out.println("ExeSeqDemo静态代码块");语句;

加载main方法所在类ExeSeqDemo后,执行main方法 —— System.out.println("进入主方法");和new ExeSeqDemo();语句,第二条语句创建ExeSeqDemo对象,则执行ExeSeqDemo构造器 —— t = new SubClass();、System.out.println("ExeSeqDemo初始化代码块");和System.out.println("ExeSeqDemo构造器");语句,同刚才讲解,依次调用SuperClass构造器和SubClass构造器,再执行后面的两条打印语句;


注:初始化顺序

当程序执行时,需要生成某个类的对象,Java执行引擎会先检查是否加载了这个类,如果没有加载,则先执行类的加载再生成对象,如果已经加载,则直接生成对象;

· 在类的加载过程中,类的static成员变量会被初始化,另外,如果类中有static语句块,则会执行static语句块;

static成员变量和static语句块的执行顺序同代码中的顺序一致;

记住,在Java中,类是按需加载,只有当需要用到这个类的时候,才会加载这个类,并且只会加载一次;


· 在生成对象的过程中,会先初始化对象的成员变量,再执行构造器

也就是说类中的变量会在任何方法(包括构造器)调用之前得到初始化,即使变量散步于方法定义之间;




二、final修饰符

1 概念

继承最大弊端是破坏封装,即子类可访问父类的实现细节,可通过方法覆盖形式修改实现细节;

多个修饰符之间没有先后关系;

final本身含义是最终的,不可变的,可修饰非抽象类、非抽象方法和变量;

构造器不能使用final修饰,因为构造方法不能被继承;


2 final用法

(1)final修饰的非抽象类

a 概念

表示最终类,该类不能被继承;


b 使用final修饰类的情形

· 某个类不是专门为继承而设计的;

· 出于安全考虑,类的实现细节不允许子类修改;

· 确认某个类不会再被拓展;


注:

Java中final修饰的类有很多,如八大基本数据类型包装类和String类;


(2)final修饰的非抽象方法

a 概念

表示最终方法,该方法不能被子类覆盖;


b 使用final修饰方法的情形

· 在父类中提供的统一的算法骨架,不允许子类通过方法覆盖进行修改;

· 在构造器中调用的方法(初始化方法)


注:

final修饰的方法,子类可调用,但不可覆盖;


(3)final修饰的变量

表示常量;

只能赋值一次,不能再赋值;

final变量必须显式地指定初始值,因为系统不会为final字段初始化;

常量名命名规范:符合标识符,单词全部使用大写字母,若由多个单词组成,单词之间使用下划线_隔开;

当在程序中,多个地方使用共同的数据,且该数据不会改变,此时定义全局的常量;


注:

常量分为字面值常量(又直接量)和final变量;

全局静态常量:使用public staitc final修饰的变量,直接使用类名调用即可;

final修饰基本数据类型变量时,该变量的值不能改变;

final修饰引用数据类型变量时,该变量引用的地址不能改变,而非引用地址所指内容不能改变;

final是唯一可以修饰局部变量的修饰符;局部内部类只能访问final修饰的局部变量;

一般在开发中,专门定义一个常量类,用以存储常量数据;

class Consts{ // 常量类public static final X_SIZE = 10;public static final Y_SIZE = 10;}




3 单例设计模式

(1)设计模式

指一套被反复使用、多数人知晓的、经过分类目的、代码设计经验的总结;

为重用代码、使代码更易被他人理解、保证代码可靠性;


(2)单例设计模式

是最常用、最简单的设计模式;

目的是保证在整个应用中某一个类有且只有一个实例(一个类在内存中只存在一个对象),即所有指向该类型实例的引用都指向同一块内存空间;


(3)编写单例设计模式的步骤(饿汉式)

1° 必须在该类中自己创建一个对象;

2° 私有化自身的构造器,防止外界通过构造器创建新的对象;

3° 向外暴露一个公共的静态方法,用于获取自身对象;

class ArrayUtil{private static final ArrayUtil instance = new ArrayUtil();private ArrayUtil(){}public static ArrayUtil getInstance(){return instance;}public void sort(int[] arr){System.out.println("排序操作");}}class SingletonDemo{public static void main(String[] args){// ArrayUtil.getInstance() == ArrayUtil.getInstance(),结果是trueArrayUtil.getInstance().sort(null);ArrayUtil.getInstance().sort(null);}}


补充:工具类的设计

工具类,存放某一类事物的工具方法的类;

工具包(util/tool/helper),存放工具类;

工具类起名XxxUtil/XxxTool/XxxHelper,其中Xxx表示一类事物,如ArrayUtil;

工具类设计方法有二:

· 若工具方法未使用static修饰,说明工具方法需使用工具类的对象调用,则将该工具类设计为单例(如上例);

· 若工具方法全部使用static修饰,说明工具方法只需使用工具类名调用,且必须把工具类的构造器私有化,防止创建无谓的工具类对象;

一般,首选第二种,因为简单;




三、基本类型包装类


八大基本数据类型的包装类都是用final修饰,都是最终类,都不能被继承;


1 装箱和拆箱

(1)装箱

把基本数据类型的值包装成对应的包装类对象;

int val = 1;Integer i1 = new Integer(val); // 方式一Integer i2 = Integer.valueOf(val); // 方式二,推荐使用!因为带有缓存


(2)拆箱

把包装类对象转换为对应的基本数据类型变量;

int num = i1.intValue();


SUN公司从Java5开始提供自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)功能;

(3)自动装箱

可直接把基本数据类型的值赋值给对应的包装类对象;

Integer i3 = 1;


(4)自动拆箱

可直接把包装类对象赋值给对应的基本数据类型变量;

Integer i4 = 1;num1 = i4;


注:

在Java集合框架中,只能存储对象,不能存储基本数据类型值;

自动装箱和自动拆箱是语法糖/编译级别的新特性,在底层依然是手动装箱和拆箱擦操作,但装箱操作使用Integer.valueOf方法,而非new Integer;

switch支持的数据类型包括byte、short、char、int,也包括前面对应的包装类Byte、Short、Character、Integer,在底层switch中对包装类对象手动拆箱;

Object o = 1; 语句是正确的,在底层包括自动装箱和引用的自动类型转换(把子类对象赋值给父类变量);

Object可接受一切数据类型值,Object[]可接受一切数据类型,如Object o1 = {"1", 2, 3.14, true};;


2 常用操作

(1)包装类中的常量

a static <基本数据类型> MAX_VALUE:表示包装类对应的基本数据类型的最大值

b static <基本数据类型> MIN_VALUE:表示包装类对应的基本数据类型的最小值

c static Boolean FALSE:表示Boolean包装类对应值为false的Boolean对象

d static Boolean TRUE:表示Boolean包装类对应值为true的Boolean对象


int maxValue = Integer.MAX_VALUE;


e static int SIZE:表示包装类对应的基本数据类型在内存中存储的位数

f static Class<包装类> TYPE:表示包装类对应的基本数据类型


(2)包装类的构造器

构造器用来创建包装类对象;

接收的参数是包装类对应的基本数据类型变量,或String类型变量(Character除外);

int num = 1;Integer i = new Integer();String str = "3.14";Double d = new Double(str);


对于Boolean包装类,在创建该对象时,接收的String变量值除"true"或"True"参数创建的包装类对象值为True,其余的都为False;

Boolean b = new Boolean("Java"); // b对象的值为False


(3)基本数据类型和包装类型的转换(装箱和拆箱)


(4)String和基本数据类型/包装类之间的转换

String和基本数据类型/包装类之间的转换,该转换方法必须在String或包装类中;

a String转换成包装类

方式一:static <包装类> valueOf(String str):表示把String转换成包装类对象

Integer i1 = Integer.valueOf("123");


方式二:new <包装类>(String str):表示使用String类型变量创建对应的包装类对象

Integer i2 = new Integer("123");


注:

无论以哪种方式,String类型值必须能够转换为赋值的包装类;


b String转换成基本数据类型

static <基本数据类型> parse<包装类>(String str):表示包装类调用parse<包装类>(String str)方法,将str转换为包装类对应的基本数据类型值;

int i = Integer.parseInt("123");


c 基本数据类型转换成String

方式一:基本数据类型变量与空双引号相加;

String str = 123 + "";


方式二:static String valueOf(<基本数据类型> num)

String str = String.valuwOf(123);


d 包装类转换成String

包装类对象调用toString()方法;

Integer i = Integer.valueOf(12);String str = i.toString();


3 包装类中的缓存设计

享元模式flyweight,本质上就是缓存设计;

Byte、Short、Integer、Long缓存[-128, 127]之间的数据;

character缓存[0, 127]之间的数据;

注:

若超过缓存区间,想比较包装类对象的数据,可调用equals()方法;


Integer i1 = new Integer(123);Integer i2 = new Integer(123);System.out.println(i1 == i2); // falseInteger i3 = Integer.valueOf(123); // 123在[-128, 127]之间,获取缓存中的数据Integer i4 = Integer.valueOf(123);System.out.println(i3 == i4); // trueInteger i5 = 123; // 自动装箱,在底层依旧是Integer.valueOf(123)Integer i6 = 123;System.out.println(i5 == i6); // trueSystem.out.println("---------------------");Integer i11 = new Integer(259);Integer i21 = new Integer(259);System.out.println(i11 == i21); // falseInteger i31 = Integer.valueOf(259); // 259不在[-128, 127]之间,使用new Integer(259)Integer i41 = Integer.valueOf(259);System.out.println(i31 == i41); // falseInteger i51 = 259;Integer i61 = 259;System.out.println(i51 == i61); // falseSystem.out.println(i51.equals(i61)) // true


4 包装类和基本数据类型的区别(以Interger和int的区别为例)

(1)默认值

int类型的默认值是0;

Integer类型的默认值是null;

推论:Integer即可表示null,又可表示0;


(2)包装类提供该类型相关的算法操作方法

如:

static String toBinaryString(int i) 表示将int类型变量i转换成二进制

static String toOctalString(int i) 表示将int类型变量i转换成八进制

static String toHexString(int i) 表示将int类型变量i转换成十六进制


(3)在集合框架中只能存储对象类型,不能存储基本数据类型


(4)方法中,基本数据类型变量存储在栈中,包装类存放在堆中


注:

开发中,建议使用包装类;




四、抽象类

1 抽象方法

(1)概念

使用abstract修饰且没有方法体的方法;


(2)特点

a 使用abstract修饰,方法没有方法体,留给子体实现;

b 抽象方法的修饰符不能是private、final、static(覆盖是对象级别的;而static修饰符是类级别的,变成隐藏,而非覆盖);

c 抽象方法必须定义在抽象类中或接口中;


注:

一般,习惯将abstract写在方法修饰符的最前面,便于识别是抽象方法;


2 抽象类

(1)概念

使用abstract修饰的类;


(2)特点

a 不能创建实例,即不能new一个对象;

b 可以不包含抽象方法,可包含普通方法;但若一旦包含,该类必须是抽象类;

c 若子类没有实现父类所有抽象方法,则子类必须作为抽象类(抽象派生类);

d 构造方法不能都定义为private,否则不能有子类(创建子类对象前先调用父类构造方法);

e 抽象类不能使用final修饰,因为必须有子类,抽象方法才得以实现;

f 抽象类是不完整的类,需作为父类,功能才得以实现;


注:

一般,对抽象类起名习惯使用Abstract作为前缀;

抽象类中可以不存在抽象方法,防止外接创建该类对象;


(3)抽象类与普通类的区别

a 普通类拥有的成员(方法、字段、构造器),抽象类都有;

b 抽象类不能创建对象,抽象类中可以包含抽象方法;


abstract class Grapgh{public abstract Double getArea();}class Circle extends Grapgh{private Integer r;Circle(Integer r){this.r = r;}public Double getArea(){return 3.14 * r * r;}}class Rectangle extends Grapgh{private Integer width;private Integer height;Rectangle(Integer width, Integer height){this.width = width;this.height = height;}public Double getArea(){return width.doubleValue() * height.doubleValue();}}public class GraphDemo{public static void main(String[] args){Circle c = new Circle(10);System.out.println(c.getArea());Rectangle r = new Rectangle(10, 10);System.out.println(r.getArea());}}




五、模板方法设计模式

1 引出模板方法思想

如下,计算不同操作所需要时间

class StringOperator {public long doWork(){long begin = System.currentTimeMillis();String str = "";for(int i = 0; i < 10000; i++){str += i;}long end = System.currentTimeMillis();return end - begin;}}class IntOperator{public long doWork(){long begin = System.currentTimeMillis();long sum = 0;for(int i = 0; i < 100000; i++){sum += i;}long end = System.currentTimeMillis();return end - begin;}}class TemplateMethodDemo{public static void main(String[] args){System.out.println(new StringOperator().doWork());System.out.println(new IntOperator().doWork());}}


在两个类的doWork方法中,有相同和不同的部分,若有一个模板类,该类中的doWork方法包含相同的代码,两个类继承该类,并编写不同的代码;



2 模板方法设计模式的概念

在父类的一个方法中,定义一个总体算法骨架(模板方法),将某些步骤延迟到子类中,因为不同子类实现细节不同;

模板方法可在不改变算法结构的情况下,重新定义算法中的某些步骤;

注:

抽象父类负责定义操作中的业务骨架,将某些具体的实现步骤延迟到子类中去实现;

抽象父类至少提供两种方法:模板方法和抽象方法

模板方法:一种通用的处理方式,即模板(总体算法的骨架);

抽象方法:一种具体的业务功能实现,由子类完成;


如下,对上例的修改,使用模板方法实现

abstract class AbstractOperateTimeTemplate{// 模板方法:总体算法骨架,子类不能修改final public long getTotalTime(){long begin = System.currentTimeMillis();this.doWork();long end = System.currentTimeMillis();return end - begin;}// 具体操作,子类必须覆盖abstract protected void doWork();}class StringOperator extends AbstractOperateTimeTemplate{public void doWork(){String str = "";for(int i = 0; i < 10000; i++){str += i;}}}class IntOperator extends AbstractOperateTimeTemplate{public void doWork(){long sum = 0;for(int i = 0; i < 100000; i++){sum += i;}}}class TemplateMethodDemo{public static void main(String[] args){System.out.println(new StringOperator().getTotalTime());System.out.println(new IntOperator().getTotalTime());}}




注:

抽象父类提供的模板方法只是定义一个通用算法,其实现必须依靠子类;

模板方法作为模板样式,不准子类覆盖,则使用final修饰;抽象方法的权限是protected;


0 0
原创粉丝点击