八、面向对象(下)
8.1 关键字:static
8.1.1 static的含义
( 1 \mathbf{1} 1) static中文是静态的意思。可以用来修饰类的属性、方法、代码块、内部类。(不可以修饰构造器)
8.1.2 static修饰属性:静态变量
( 1 \mathbf{1} 1) 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量):
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
( 2 \mathbf{2} 2) static修饰属性的其他说明:
-
静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用。不用再造了对象才去调用。
-
例:
-
public class ObjectTest { @Test public void test1() { Chinese.nation = "China"; Chinese c1 = new Chinese(26); Chinese c2 = new Chinese(29); System.out.println(c1.nation); System.out.println(c2.nation); c1.nation = "CHN"; System.out.println(c1.nation); System.out.println(c2.nation); } } class Chinese { static String nation;//静态变量(类变量) int age; public Chinese() { } public Chinese(int age) { this.age = age; } }
输出:
China China CHN CHN
-
静态变量的加载要早于对象的创建。
-
由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
可调用吗? | 类变量 | 实例变量 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
- 类变量和实例变量的内存解析
8.1.3 static修饰方法:静态方法
( 1 \mathbf{1} 1) 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用。
可调用吗? | 静态方法 | 非静态方法 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
( 2 \mathbf{2} 2) 调用的限制:
- 静态方法中,只能调用静态的方法或属性。
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
( 3 \mathbf{3} 3) static注意点:
-
在静态的方法内,不能使用this关键字和super关键字。
-
关于静态属性和静态方法的使用,从生命周期的角度去理解。
-
静态属性和静态方法是随着类的加载而产生,随着类的销毁而消亡。贯穿在多个对象。
( 4 \mathbf{4} 4) 开发中,如何确定一个属性是否要声明为static的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为static。
( 5 \mathbf{5} 5) 开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法,通常设置为static的。
- 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections。
8.1.4 static应用
( 1 \mathbf{1} 1) 自动赋值ID
- 在新建对象的时候自动依次赋值ID。
例:
① Chinese类
class Chinese {
static String nation;
int age;
private int id;
private static int init = 1001;
public Chinese() {
id = init++;
}
public Chinese(int age) {
this();
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
② 测试
public class ObjectTest {
@Test
public void test1() {
Chinese c1 = new Chinese(26);
System.out.println("c1's ID: " + c1.getId());
Chinese c2 = new Chinese(29);
System.out.println("c1's ID: " + c2.getId());
}
}
③ 输出:
c1's ID: 1001
c1's ID: 1002
( 2 \mathbf{2} 2) 题目2
-
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。
-
编写main类,使用银行账户类,输入、输出3个储户的上述信息。(考虑:哪些属性可以设计成static属性。)
8.1.5 单例设计模式
( 1 \mathbf{1} 1) 设计模式的概念
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
( 2 \mathbf{2} 2) 单例模式1:饿汉式
//饿汉式
class Bank{
//1.私有化构造器,防止在类外部新建对象
private Bank(){
}
//2.在类内部新建静态对象,并私有化
private static Bank instance = new Bank();
//3.提供public方法供外部调用,返回这个对象
public Bank getInstance(){
return instance;
}
}
( 3 \mathbf{3} 3) 单例模式2:懒汉式
//懒汉式
class Person {
//1.私有化构造器,防止在类外部新建对象
private Person() {
}
//2.在类内部声明静态对象,并私有化,但没有初始化
private static Person instance = null;
//3.提供public的静态方法供外部调用,返回这个对象
public static Person getInstance() {
if (instance == null) {
instance = new Person();
}
return instance;
}
}
( 4 \mathbf{4} 4) 饿汉式和懒汉式对比
饿汉式 | 懒汉式 |
---|---|
好处:饿汉式是线程安全的 | 好处:延迟对象的创建。 |
坏处:对象加载时间过长 | 目前的写法坏处:线程不安全。—>到多线程内容时,再修改 |
(
5
\mathbf{5}
5) 单例模式的优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可
以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方
式来解决。
( 6 \mathbf{6} 6) 单例模式的应用场景
8.2 main方法的理解
public static void main(String[] args) {
}
( 1 \mathbf{1} 1) main()方法作为程序的入口。
( 2 \mathbf{2} 2) main()方法也是一个普通的静态。
( 3 \mathbf{3} 3) main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)。
8.3 类成员四:代码块
8.3.1 代码块的含义
( 1 \mathbf{1} 1) 代码块是声明在类中,以大括号框住的一段代码。用于进行初始化,分为静态代码块和非静态代码块。
( 2 \mathbf{2} 2) 代码块如果有修饰的话,只能使用static.
class Person{
static{
静态代码块语句;
}
{
非静态代码块语句;
}
}
8.3.2 静态代码块
- 内部可以有输出语句。
- 随着类的加载而执行,而且只执行一次。
- 作用:初始化类的信息。
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
- 静态代码块的执行要优先于非静态代码块的执行。
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。
8.3.3 非静态代码块
-
内部可以有输出语句。
-
随着对象的创建而执行。
-
每创建一个对象,就执行一次非静态代码块。
-
作用:可以在创建对象时,对对象的属性等进行初始化。
-
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
-
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
8.3.4 执行顺序总结
- 由父及子,静态先行。
例:
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();
}
}
输出:
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
-
对属性可以赋值的位置:
①默认初始化
②显式初始化/⑤在代码块中赋值
③构造器中初始化
④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
-
执行的先后顺序:① - ② / ⑤ - ③ - ④
-
② 和⑤是谁先写谁执行。
8.4 关键字:final
8.4.1 final的含义
- final可以修饰的结构:类、方法、变量。
- 当final修饰类时:最终子类,不能再向下延伸子类,不能被继承。
- 当final修饰方法时:表明方法不能被重写。
- 当final修饰变量时:表面变量不能被修改。即常量。
- final修饰属性时,可以赋值的位置有:显式赋值、在代码块中赋值、构造器中赋值。
- final修饰局部变量时,是常量,用全大写。也可以用在形参。
- static final 可以修饰属性:全局常量。
- static final 可以修饰方法。
8.5 抽象类与抽象方法
8.5.1 抽象的含义
- 希望增强父类的通用性,还未创建子类的时候,不知道父类的一些方法该怎么写。于是出现了抽象类模糊化,使父类的方法不必写得太具体,增强了父类的通用性。
- 抽象类可以继承非抽象类。
- 关键字:abstract。可以修饰:类和方法。
- abstract 不可以修饰:
- 不可以修饰属性、构造器等结构。
- 不可以修饰私有方法、静态方法、final方法和final类。
8.5.2 抽象类的特点
-
一旦类被声明为抽象类,则该类不能被实例化(不能创建对象)。具体的执行需要子类实现。
-
抽象类的声明:用关键字——abstract
abstract class 类名{ }
-
抽象类中,同样有属性、构造器和方法。以供子类继承。可以这样理解:抽象类就是比普通类多定义了抽象方法,除了不能直接进行实例化操作之外,并没有任何不同。
-
包含抽象方法的类,一定是抽象类。
8.5.3 抽象方法的特点
-
抽象方法的声明:
public abstract void method();
-
抽象方法必须声明在抽象类中。
-
抽象方法没有方法体。不写大括号。
-
继承了抽象类的子类,必须重写所有父类(包括间接父类)中的抽象方法,才能创建实例对象。否则,这个子类依然是抽象类,要用 abstract 修饰。
8.5.4 abstract使用注意点
- abstract 不能修饰属性、构造器和代码块。
- abstract 不能修饰以下:
- 私有方法:因为私有方法不能被类以外的子类调用,因此没办法被重写。一直不能被重写就一直无法创建实例对象。
- 静态方法:因为方法前用static修饰,不认为是重写。
- final方法:final修饰的方法不能被重写。
- final类:因为final类不能被继承。
8.5.5 创建抽象类的匿名子类对象
-
跟第六章的匿名对象一样,当我们只想用一次抽象类的子类对象去传形参时,我们可以创建抽象类的匿名子类对象。
-
创建匿名子类的对象:
/*其中,Person是抽象类,不能实例化。 *下面演示创建抽象类的匿名子类对象 */ Person p = new Person(){ @Override public void eat() { System.out.println("吃东西"); } @Override public void breath() { System.out.println("好好呼吸"); } };
-
更懒一点,甚至可以直接创建匿名子类的匿名对象:
//其中,method1(Object obj)方法的形参必须传入对象 method1(new Person(){ @Override public void eat() { System.out.println("吃好吃东西"); } @Override public void breath() { System.out.println("好好呼吸新鲜空气"); } });
8.5.6 模板方法设计模式
8.6 接口(interface)
8.6.1 接口的含义
- 解决了类不能被多继承的缺陷,接口可以得到多重继承的效果。
- 实现接口有点像继承父类,就能拥有接口中声明的方法了。
- Java中,一个类可以实现多个接口。但父类只能有一个。
- 抽象类可以实现 (implements) 接口。
- Java中,接口和类是并列的结构。
- 用关键字:interface 来声明。
8.6.2 接口的成员
-
接口声明格式:
interface Flyable { //全局常量 public static final int MAX_SPEED = 7900;//第一宇宙速度 int MIN_SPEED = 1;//前面修饰符可省略,自动生效 //抽象方法 public abstract void fly(); void stop();//同可省略 }
-
JDK7及以前:只能定义全局常量和抽象方法。
- 全局常量:public static final 的。但是书写时可省略,但还是生效的。
- 抽象方法:public abstract 的。
-
JDK8及以后:除了以上两种,新增了
静态方法
和默认方法
。 -
接口中,不能定义构造器。意味着,接口不可以实例化。
-
Java开发中,接口通过让类去实现 (implement) 的方式来使用。
- 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。
- 否则,此实现类仍为抽象类。
class Plane implement Flyable{ @Override public void fly(){ } @Override public void stop(){ } }
8.6.3 接口的多实现与接口的继承性
-
Java类可以实现多接口,可以弥补单继承性的缺陷。
-
多实现格式:
class Bullet extends Object implements Flyable, Attackale { @Override public void attack(){ } @Override public void fly(){ } @Override public void stop(){ } }
- 其中,Bullet是子类,Object是父类。继承 extends 应该写在实现 implements 前面。多个接口名用逗号分隔。
- 若想让此类实例化,则必须重写接口中的所有抽象方法。
-
接口与接口之间可以继承,且可以多继承。
-
接口多继承格式:
interface AA { void method1();//省略了前面3个修饰符 } interface BB { void method2(); } interface CC extends AA, BB { 自动继承了AA和BB的所有抽象方法; }
-
接口的具体使用体现多态性。
-
接口的本质是契约、标准、规范。
8.6.4 匿名接口的创建
-
- 创建了接口的非匿名实现类的非匿名对象:
Flash flash = new Flash();
-
- 创建了接口的非匿名实现类的匿名对象:
com.transferData(new Printer());
-
- 创建了接口的匿名实现类的非匿名对象:
USB phone = new USB() { @Override public void start() { System.out.println("手机开始工作"); } @Override public void stop() { System.out.println("手机结束工作"); } }
-
- 创建了接口的匿名实现类的匿名对象:
com.transferData(new USB() { @Override public void start() { System.out.println("mp3开始工作"); } @Override public void stop() { System.out.println("mp3结束工作"); } }
8.6.5 接口的设计模式:代理模式
8.6.6 接口练习
-
题目1
interface A { int x = 0; } class B { int x = 1; } class C extends B implements A { public void pX() { //编译不通过。因为x是不明确的 // System.out.println(x); System.out.println(super.x);//1 System.out.println(A.x);//0 } public static void main(String[] args) { new C().pX(); } }
如果在接口和父类重名的情况下,想调用接口的x,直接A.x即可。因为接口中的变量是全局常量。如果想调用父类中的x,则直接super.x即可。
8.6.7 Java8中接口的新特性
-
接口中定义的静态方法,只能通过接口调用。
-
接口中定义的默认方法,通过接口的实现类对象调用。
-
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写的方法。
-
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>
类优先原则
-
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。–>
接口冲突
。
例子:
public interface CompareA {
//静态方法:只能通过接口来调用
public static void method1() {
System.out.println("CompareA: 北京");
}
//默认方法: 实现类的对象中才能用
public default void method2() {
System.out.println("CompareA: 上海");
}
}
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1(); //报错,不能通过实现类调用接口的静态方法
CompareA.method1();//通过接口来调用静态方法
s.method2();//实现类的对象中调用默认方法
}
}
class SubClass implements CompareA {
}
输出:
CompareA: 北京
CompareA: 上海
-
如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){ method3();//调用自己定义的重写的方法 super.method3();//调用的是父类中声明的 //调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3(); }
8.7 类成员五:内部类
8.7.1 内部类的含义
-
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。
-
内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)。
-
在外部定义的一些类,如果在外面再也用不着,就可以考虑定义为内部类。
-
成员内部类:
-
一方面,作为外部类的成员:
-
调用外部类的结构:Person.this.eat();
-
可以被static修饰
-
可以被4种不同的权限修饰
-
另一方面,作为一个类:
-
类内可以定义属性、方法、构造器等
-
可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
-
可以被abstract修饰
-
-
4.关注如下的3个问题
-
4.1 如何实例化成员内部类的对象
//创建static的Dog实例(静态的成员内部类) //其中,Person是外部类,Dog是static的内部类 Person.Dog dog = new Person.Dog();
//创建非静态Bird实例(非静态的成员内部类) //其中,Person是外部类,Bird是非static的内部类 Person p = new Person(); Person.Brid bird = new p.new Bird();
-
4.2 如何在成员内部类中区分调用外部类的结构
-
例:在外部类属性名、内部类属性名和内部类方法形参名同名时,如何区分这3个结构:
class Person { String name = "小明"; //非静态成员内部类 class Bird { String name = "杜鹃"; public void display(String name) { System.out.println(name);//该方法形参 System.out.println(this.name);//内部类属性"杜鹃" System.out.println(Person.this.name);//外部类属性"小明" } } }
-
4.3 开发中局部内部类的使用。
例:
//外部类Person class Person { //在方法中的局部内部类 public void method() { class AA { } } //在代码块中的局部内部类 { class BB { } } //在构造器中的局部内部类 public Person() { class CC { } } }
-
8.7.2 注意点
-
在局部内部类的方法中(比如下面的show),如果调用外部类所声明的方法(比如下面的method)中的局部变量(比如num),则要求此局部变量必须声明为 final 的。
-
在 JDK8 以后,如果忘记写 final ,编译器默认会为你省略,但 final 依然生效。
//外部类InnerClassTest class InnerClassTest { //外部类的方法method() public void method() { //外部类的方法中的局部变量num int num = 10; //final int num = 10;//JDK7及以前必须声明final //局部内部类AA class AA { //局部内部类AA的方法show() public void show() { //num = 20;//报错,final变量不能改 System.out.println(num); } } } }
8.7.3 总结
-
成员内部类和局部内部类在编译以后,都会生成字节码文件。
格式:
成员内部类:外部类$内部类名.class 局部内部类:外部类$数字 内部类名.class