第6章 面向对象编程-下
第6章、面向对象编程-下
目录
- 一、关键字:static
- 单例(Singleton)设计模式【掌握,可能在笔试时手写】
- 二、理解 main 方法的语法
- 三、类的成员之四:代码块(或初始化块)
- 四、关键字:final
- 五、抽象类与抽象方法【少数认为归为特征(封装、继承、多态、抽象),一般归为关键字:abstract】
- 六、接口(interface)【关键字的使用】
- 七、类的成员之五:内部类(自己写比较少,但源码中会出现)
- 面试题
- 每日一考
- 声明抽象类,并包含抽象方法,测试类中创建一个继承抽象类的匿名子类对象
一、关键字:static
- static:静态的
- static可用来修饰:属性、方法、代码块、内部类
- 使用static修饰属性:静态变量(或类变量)
属性:按是否使用static修饰,又分为静态属性 vs 非静态属性(实例变量)
- 实例变量:创建类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
- 静态变量:创建类的多个对象,多个对象共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
static修饰属性的其他说明
- 静态变量随着类的加载而加载,可通过“类.静态变量”的方式进行调用
- 静态变量加载要早于对象的创建
- 由于类只会加载一次,则静态变量也只会存在一份,存在方法区的静态域中
静态属性举例
- System.out
- Math.PI
类变量 vs 实例变量的内存解析
- 使用static修饰方法:静态方法
- 随着类的加载而加载,可通过“类.静态方法”的方式调用
- 静态方法中只能调用静态的方法或属性。非静态方法中即可调用非静态的方法和属性、也可调用静态的方法和属性
- static注意点
- 静态方法中,不能使用this关键字和super关键字(this和super都是基于有当前对象才可调用的)?super调用父类中的static结构也不可
- 静态属性和静态方法的使用,可从生命周期的角度分析
- 开发中,如何确定一个属性是否要声明为static的?
- 属性可被多个对象所共享,不会随对象的不同而不同时
- 类中的常量也通常声明为static
- 开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法通常设置为static的
- 工具类中的方法,习惯上声明为static的,如Math、Arrays、Collections
单例(Singleton)设计模式【掌握,可能在笔试时手写】
- 设计模式 定义
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
- 单例设计模式 定义
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。
- 因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
- 单例设计模式实现【笔试时写线程安全的,不要写线程不安全的代码】
- 饿汉式单例模式实现(先把对象创建出来)
- 懒汉式单例设计模式(不用对象不造,用的时候才造对象)
1 | // 饿汉式(单例模式) |
- 区分饿汉式和懒汉式
- 饿汉式
- 坏处:对象加载的时间过长(先加载出来对象,有可能长时间不用)
- 好处:是线程安全的
- 懒汉式:
- 好处:延迟对象的创建
- 目前写法的坏处:线程不安全 --->多线程项目时需修改
- 饿汉式
- 单例模式的优点
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 举例:java.lang.Runtime
- 单例模式的应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
二、理解main方法的语法
- main()方法作为程序的入口
- main()方法也是一个普通的静态方法
- main()方法可作为我们与控制台交互的方式(之前用的是Scanner)
三、类的成员之四:代码块(或初始化块)
- 代码块的作用:用来初始化类、对象
- 代码块如有修饰,只能使用static。因此代码块分为静态代码块 vs 非静态代码块
- 静态代码块
- 格式:static { ... }
- 内部可有输出语句
- 随着类的加载(使用到类时加载)而执行,只会执行一次,除非类被重新加载
- 作用:初始化类的信息
- 如一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行优先于非静态代码块的执行(类和对象加载先后)。静态代码块只能调用静态属性、方法,不能调用非静态的结构
- 非静态代码块
- 格式:{ ... }
- 内部可有输出语句
- 随着对象的创建而执行,每创建一个就执行一次
- 作用:在创建对象时,对对象的属性进行初始化
- 如一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块既可调用静态属性、方法,也可调用非静态的结构
- 静态代码块、构造器、静态方法(包含main)、非静态代码块执行的先后顺序:由父及子,静态先行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49package codeblock;
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
//new Leaf();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package codeblock;
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
} - 属性赋值的先后顺序:默认初始化 - 显式初始化 / 代码块(先后顺序) - 构造器中赋值 - 创建对象后通过对象.方法或对象.属性赋值
四、关键字:final
final:最终的 1. final可用来修饰:类、方法、变量 2. final修饰类:此类即不可被其他类继承 - 例如:String类、System类、StringBuffer类 3. final修饰方法:此方法不可被重写 - 例如:Object类中的getClass()方法【native关键字表示其方法实现采用C/C++代码实现】 4. final修饰变量:此“变量”即为一个常量 - final修饰属性:可赋值的位置有显式初始化、代码块中赋值、构造器中初始化【不可使用默认值,不可采用set方法赋值(类的构造器执行完毕后对象及其内部结构就应被初始化,而方法晚于对象及属性初始化)】 - final修饰局部变量: - 尤其使用final修饰形参时,表明此形参为一个常量,当调用此方法时,给常量形参赋一个实参,一旦赋值后,就只能在方法体内使用此形参,不可进行赋值等修改操作
- static final可用来修饰:属性、方法
- 修饰属性:全局(static)常量(final)
五、抽象类与抽象方法【少数认为归为特征(封装、继承、多态、抽象),一般归为关键字:abstract】
- 抽象类定义
- 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
abstract关键字的使用:abstract可用来修饰类、方法
abstract修饰类
- 抽象类不能实例化
- 抽象类中一定有构造器,子类实例化时会调用
- 开发中,需提供抽象类的子类,让子类实例化,完成开发工作
abstract修饰方法
- 抽象方法只有方法的声明,无方法体
- 包含抽象方法的类一定是抽象类,反之抽象类中可以没有抽象方法
- 若子类重写了父类中的所有抽象方法,此类方可实例化;若子类没有重写父类中的所有抽象方法,此类仍需是抽象类,需使用abstract修饰类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19abstract class Person{
// 抽象方法
public abstract void eat();
}
// 若子类重写了父类中的所有抽象方法,此类方可实例化
class Stu extends Person{
// Override
public void eat(){
...
}
}
// 若子类没有重写父类中的所有抽象方法,此类仍需是抽象类
abstract class Man extends Person{
public Man(){
}
}
abstract使用上的注意点:不可修饰属性、构造器;不可修饰私有方法、静态方法和final方法(私有方法和静态方法无法重写)
举例l
IO流中涉及到的抽象类:InputStream / OutputStream / Reader / Writer,在其内部定义了抽象的read()、write()方法
抽象类的匿名子类
1 | // Person为抽象类,含有一个eat抽象方法 |
模板方法设计模式(TemplateMethod)【抽象类的应用】
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题: - 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。 - 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
1 | abstract class Template{ |
六、接口(interface)【关键字的使用】
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能" 的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口的使用(JDK7及之前)
接口使用interface来定义
Java中,接口和类是并列的两个结构
如何定义接口,定义接口中的成员
- JDK 7及以前,只能定义全局常量和抽象方法
- 全局常量:public static final的(可省略不写,但仍是全局常量)
- 抽象方法:public abstract的
- JDK 8:除了定义全局常量和抽象方法之外,还可定义静态方法、默认方法
- JDK 7及以前,只能定义全局常量和抽象方法
接口中不能定义构造器,意味着接口不可实例化
Java开发中接口通过让类去实现(implements)的方式使用。如果实现类覆盖了接口中所有抽象方法,此实现类即可实例化;否则,该类是抽象类,需使用abstract修饰
类中把abstract方法重写其实是实现,子类覆盖父类的方法叫重写
Java类可实现多个接口(多实现) ---> 弥补了Java单继承的局限性。
- 格式:class AA extends BB implements CC, DD, EE{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37interface Attackable{
void attack();
}
interface Flyable{
// 全局常量
public static final int MAX_SPEED = 7900; //第一宇宙速度
int MIN_SPEED = 1; // 省略了public static final
// 抽象方法
public abstract void fly();
void stop(); // 省略了public abstract
}
class Bullet extends Object implements Flyable, Attackable{
public void attack() {
// TODO Auto-generated method stub
}
public void fly() {
// TODO Auto-generated method stub
}
public void stop() {
// TODO Auto-generated method stub
}
}
- 格式:class AA extends BB implements CC, DD, EE{}
接口和接口之间可以多继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27interface AA{
void method();
}
interface BB{
void method1();
}
interface CC extends AA, BB{
}
class Test implements CC{
public void method() {
// TODO Auto-generated method stub
}
public void method1() {
// TODO Auto-generated method stub
}
}接口的具体使用,体现多态性
接口实际上可看作是一种规范(例如USB是一种规范,具体的硬盘、U盘、打印机都需要去实现USB规范中的具体操作,驱动即是接口实现类的集合)
开发中,体会面向接口编程
- 接口的主要用途就是被实现类实现(面向接口编程)
- 项目的具体需求是多变的,我们必须以不变应万变才能从容开发,此处的不变就是规范,因此,开发项目往往是面向接口编程
- 例子:面向接口编程,在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个数据库厂商的API
- 创建实现类对象的四种类型
- 非匿名实现类的非匿名对象
- 非匿名实现类的匿名对象
- 匿名实现类的非匿名对象
- 匿名实现类的匿名对象
接口的应用
- 代理模式(Proxy)
- 应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
- 分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
- JDK自带的动态代理,需要反射等知识
- 应用场景
- 工厂模式
- 简单工厂模式:class XxxFactory{}。创建Xxx对象的类
- 工厂方法模式
- 抽象工厂模式
接口的使用(JDK8)
除了定义全局常量和抽象方法之外,还可定义静态方法、默认方法
1 | public interface InterfaceJDK8 { |
接口中定义的静态方法,只能通过接口来调用(接口名.静态方法),实现类无法调用
实现类的对象可调用接口中的默认方法。如果实现类重写了接口中的默认方法(重写时不加default关键字),调用时调用的是重写后的方法
如果子类或实现类继承的父类 和 实现的接口中声明了同名同参数的方法(默认方法),在子类没有重写此方法的情况下,调用的是父类中的同名同参数方法 --> 类优先原则(只是指方法,属性不适用)
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,,那么在实现类没有重写此方法的情况下,报错 --> 接口冲突。需要在实现类中重写此方法
在实现类方法中调用父类、接口中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14class SubClass extends SuperClass implements InterfaceA, InterfaceB{
public void method3(){
}
public void method(){
// 假设method3为SuperClass、InterfaceA、InterfaceB中有方法体的同名同参数方法
method3(); //调用自己重写的方法
super.method3(); //调用父类中的方法
InterfaceA.super.method3(); //调用InterfaceA接口中的method3()方法
InterfaceB.super.method3(); //调用InterfaceB接口中的method3()方法
}
}
七、类的成员之五:内部类(自己写比较少,但源码中会出现)
Java中允许将一个类A声明在另一个类B中,则A为内部类,类B为外部类
内部类分类:成员内部类 vs 局部内部类(方法内、代码块内、构造期内...)
成员内部类
- 作为外部类的成员:
- 可调用外部类结构
- 可被static修饰(内部类可被static修饰,外部类不可)
- 作为成员,可被四种权限修饰
- 作为一个类:
- 类内部定义属性、方法、构造器等
- 可被final修饰,表示此类不可被继承。反之可被继承
- 可被abstract修饰
- 作为外部类的成员:
关注的3个问题
如何实例化成员内部类对象
1
2
3
4
5
6
7
8
9
10
11
12
13public class InnerClassTest {
public static void main(String[] args) {
// 创建Dog实例(静态成员内部类)
Person.Dog dog = new Person.Dog();
// 创建Bird实例(非静态成员内部类)
Person p = new Person();
Person.Bird bird = p.new Bird();
}
}如何在成员内部类中区分调用外部类结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class Person{
String name;
public void method(){
// 局部内部类
class AA{
}
}
// 静态成员内部类
static class Dog{
// 属性、构造器、方法、代码块
}
// 非静态成员内部类
class Bird{
// 属性、构造器、方法、代码块
String name;
public void display(String name){
System.out.println("形参:" + name);
System.out.println("内部类name:" + this.name);
System.out.println("外部类name:" + Person.this.name);
}
}
}开发中局部内部类的调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public class InnerClassTest1 {
// 开发中少见
public void method(){
// 局部内部类
class AA{
}
}
// 返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
// 方式一
// 创建一个实现了Comparable接口的类: 局部内部类
class MyComparable implements Comparable{
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
}
return new MyComparable();
// 方式二
return new Comparable(){
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
}
}
}
- 注意点
- 在局部内部类方法中,如要在局部内部类所声明的方法中调用内部类所在方法中的局部变量的话,要求此变量声明为final的。JDK7之前,要求此局部变量显式声明为final,JDK8及之后,可省略显式声明。(移动端开发 (android开发)使用较多)【原因:每个类都会生成一个字节码文件,但是方法中的局部变量作用域和字节码文件作用域不同,因此需要声明局部变量为final,且传给内部类中的方法的变量值为该局部变量的副本】
- 总结
- 成员内部类和局部内部类,在编译后都会生成字节码文件。格式:
- 成员内部类:外部类名$内部类名.class,例如:Person$Dog.class
- 局部内部类:外部类名$数字内部类名.class,例如:Person$1Bird.class
面试题
final 排错
1
2
3
4
5
6public class Something {
public int addOne(final int x) {
return ++x;
// return x + 1;
}
}1
2
3
4
5
6
7
8
9
10
11
12public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other();
o.i++;
}
}
class Other {
public int i;抽象类和接口有哪些异同?
- 相同点:
- 不可实例化,都包含抽象方法;
- 不同点:
- 抽象类和接口定义、内部结构解释说明(JDK7及前,JDK8,JDK9)
- 抽象类有构造器,接口无构造器;
- 单继承和多继承
- 类与接口的关系:多实现
- JDK8新特性
- JDK9新特性
- 相同点:
排错
1
2
3
4
5
6
7
8
9
10
11
12
13
14interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x); // 编译报错,变量不明确
}
public static void main(String[] args) {
new C().pX();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable{
private String name;
public String getName(){
return name;
}
public Ball(String name){
this.name = name;
}
public void play(){
ball = new Ball("Football");
// interface中声明和初始化的ball为常量,不可赋值,此处错误
System.out.println(ball.getName());
}
}
每日一考
static修饰的属性,相较于实例变量,有哪些特别之处
- 随着类的加载而加载,早于对象的创建,只要权限允许,可通过对象.属性的方式进行调用,存在于方法区的静态域中。
final可用来修饰那些结构,分别表示什么意思
- 修饰类(不可继承)、方法(不可重写)、属性(常量)、局部变量(显式初始化、构造器、代码块)
代码实现单例模式的饿汉式和懒汉式
类的属性赋值位置有哪些?先后顺序为?
- 默认初始化 - 显式初始化 / 代码块 - 构造器 - 创建对象后采用对象.属性或对象.方法方式赋值
abstract能修饰那些结构?修饰以后,有什么特点?
- abstract可修饰类、方法
- 抽象类无法直接创建对象,必须要被继承并重写内部的所有抽象方法才能创建对象
- 抽象方法无方法体,只定义了一种功能的标准,具体的执行过程,需要子类去实现
- abstract可修饰类、方法
接口是否能继承接口?抽象类是否能实现接口?抽象类是否能继承非抽象的类
- 接口可以继承接口,且可以多继承
- 抽象类可以实现接口
- 抽象类可以继承非抽象的类
声明抽象类,并包含抽象方法,测试类中创建一个继承抽象类的匿名子类对象
抽象类和接口有哪些共同点和区别?
- 抽象类和接口都无法直接创建对象,都可被继承
- 抽象类有构造器,接口不能声明构造器;单继承 vs 多继承;
如何创建静态成员内部类和非静态成员内部类的对象?
- 静态成员内部类的对象:外部类.内部类 引用名 = new 外部类.内部类();
- 非静态成员内部类对象:外部类.内部类 引用名 = 外部类对象名.new.(外部类.)内部类();
static、final、abstract分别能用来修饰什么?联系和区别总结。