05 面向对象(下)
5.1 关键字:static
5.1.1 概念及使用
概念
1.static:静态的。
2.static 可以用来修饰:属性、方法、代码块、内部类。
3.使用 static 修饰属性:静态变量(或类变量)。
3.1 属性:是否使用 static 修饰,又分为:静态属性 VS 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有了一套类中的非静态属 性。当修改其中一个非静态属性时,不会导致其他对象中同样的属性值的修饰。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过静态变量去修改某一个变量时,会导致其他对象调用此静态变量时,是修改过的。
3.2 static 修饰属性的其他说明:
① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用。
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一次。存在方法区的静态域中。
④ 类变量 实例变量
类 yes no
对象 yes yes
通俗说
一套三室一厅一厨一卫的房子出租,三间卧室相当于类的三个实例化对象,其中有各自的属性如床、柜子、桌子等,彼此之前互不干扰,而厕所和厨房就像是static所修饰的属性,是所有人共用的,一个人修改了其余人的也更改。
示例
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";//实例化前就有了
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN";
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA";
System.out.println(c1.nation); //CHINA,二者共用的nation
//编译不通过
// Chinese.name = "张继科";
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
}
5.1.2 类变量的内存解析
![img](https://i-blog.csdnimg.cn/blog_migrate/6a210c103f942bc13d287e2d6044ce25.png)
5.1.3 static修饰方法
概念
1.使用 static 修饰方法:静态方法
① 随着类的加载而加载,可以通过"类.静态方法"的方式调用
② 静态方法 非静态方法
类 yes no
对象 yes yes
③ 静态方法中,只能调用静态的方法或属性!!!
非静态的方法中,可以调用所有的方法或属性
2.static 注意点:
2.1 在静态的方法内,不能使用 this 关键字、super 关键字
2.2 关于静态属性和静态方法的使用,从生命周期的角度去理解。
3.开发中,如何确定一个属性是否需要声明 static 的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为 static
开发中,如何确定一个方法是否要声明为 static 的?
- 操作静态属性的方法,通常设置为 static 的
- 工具类中的方法,习惯上声明为 static 的。比如:Math、Arrays、Collections
5.1.4 题目示例
//static 关键字的应用
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
Circle c3 = new Circle(3.4);
System.out.println("c1 的 ID:" + c1.getId());//1001
System.out.println("c2 的 ID:" + c2.getId());//1002
System.out.println("c3 的 ID:" + c3.getId());//1003
System.out.println("创建圆的个数: " + Circle.getTotal());//3
}
}
class Circle{
private double radius;
private int id; //需要自动赋值
public Circle(){
id = init++;
total++;
}
public Circle(double radius){
this();
this.radius = radius;
}
private static int total;//记录创建圆的个数
private static int init = 1001;//static 声明的属性被所有对象所共享
public double findArea(){
return 3.14 * radius * radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public static int getTotal() {
return total;
}
}
5.1.5 单例设计模式
概念
1.所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
2.如何实现?
饿汉式 VS 懒汉式
3.区分饿汉式和懒汉式。
饿汉式:坏处:对象加载时间过长。
好处:饿汉式是线程安全的。
懒汉式:好处:延迟对象的创建。
坏处:目前的写法,会线程不安全。—》到多线程内容时,再修改
代码示例1—饿汉式
public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);//true,因为只有一个对象
}
}
//单例的饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创见类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象。
public static Bank getInstance(){
return instance;
}
}
代码示例2—懒汉式
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);//true
}
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化。
//此对象也必须声明为 static 的
private static Order instance = null;
//3.声明 public、static 的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
5.2 main()方法的语法(了解)
使用说明
1.main()方法作为程序的入口;
2.main()方法也是一个普通的静态方法
3.main()方法也可以作为我们与控制台交互的方式。(之前,使用 Scanner)
5.3 代码块(类的成员之四)💛
5.3.1 代码块概念
概念
1.代码块的作用:用来初始化类、对象的
2.代码块如果有修饰的话,只能使用static
3.分类:静态代码块 vs 非静态代码块
-
静态代码块
-
内部可以有输出语句
-
**随着类的加载而执行**,而且**只执行一次**
-
作用:初始化类的信息
-
如果一个类中,定义了多个静态代码块,则按照声明的先后顺序执行
-
静态代码块的执行,**优先于非静态**代码块的执行
-
静态代码块内**只能调用静态的属性、静态的方法**,不能调用非静态的结构
-
-
非静态代码块
-
内部可以有输出语句
-
随着对象的创建而执行
-
每创建一个对象,就执行一次非静态代码块。
-
作用:可以在创建对象时,对对象的属性等进行初始化。
-
如果一个类中,定义了多个非静态代码块,则按照声明的先后顺序执行
-
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
-
5.3.2 变量赋值执行顺序💛
构造器、静态、非静态的先后顺序
见Day5中的Son.java和LeafTest.java中的例子
静态块–>非静态块–>构造器
属性赋值先后循序
对属性可以赋值的位置:
①默认初始化
②显式初始化 / ⑤在代码块中赋值 //看谁先谁后
③构造器中初始化
④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值。
执行的先后顺序:① - ② / ⑤ - ③ - ④
子类父类内部执行顺序
父类静态代码块 ->子类静态代码块 ->父类非静态代码块 -> 父类构造函数 -> 子类非静态代码块 -> 子类构造函数。
5.4 关键字:final
概念
- final:最终的
-
final可以用来修饰的结构:类、方法、变量
-
final用来修饰一个类:此类不能被其他类所继承。
- 比如:String类、System类、StringBuffer类
-
final修饰一个方法:final标记的方法不能被子类重写。
- 比如:Object类中的getClass()。
-
final用来修饰变量:此时的"变量"(成员变量或局部变量)就是一个常量。名称大写,且只能被赋值一次。
-
final修饰属性,可以赋值的位置有:显式初始化、代码块中初始化、构造器中初始化(每个都要赋值)
-
final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
-
-
static final 用来修饰:全局常量
代码示例
final int WIDTH = 0;
final int LEFT;
final int RIGHT;
{
LEFT = 1;
}
public FinalTest(){
RIGHT = 2;
}
public FinalTest(int n){
RIGHT = n;
}
5.5 抽象类与抽象方法
5.5.1 概念
概念
-
abstract:抽象的
-
abstract 可以用来修饰的结构:类、方法
-
abstract 修饰类:抽象类
-
此类不能实例化
-
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化全过程)
-
开发中,都会提供抽象类的子类,让子类对象实例化,实现相关的操作
-
-
abstract 修饰方法:抽象方法
-
抽象方法,只有方法的声明,没有方法体。public abstract void eat();
-
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法
-
若子类重写了父类中所有的抽象方法,此子类方可实例化。
若子类没有重写了父类中所有的抽象方法,则此子类也是一个抽象类。
-
-
abstract 使用上的注意点:
-
abstract 不能用来修饰变量、代码块、构造器;
-
abstract 不能用来修饰私有方法、静态方法、final 的方法、final 的类。
-
5.5.2 抽象类的匿名子类
PersonTest类
public class PersonTest {
public static void main(String[] args) {
method(new Student()); //匿名对象
Worker worker = new Worker();
method1(worker); //非匿名的类非匿名的对象
method1(new Worker()); //非匿名的类匿名的对象
System.out.println("*********************");
//创建了一个匿名子类的对象:p //这样如果只造一个对象就没必要新造一
Person p = new Person(){ //个类了,这样创建匿名子类更方便
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("呼吸空气");
}
};
method1(p);
System.out.println("**********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃零食");
}
@Override
public void breath() {
System.out.println("云南的空气");
}
});
}
public static void method1(Person p){
p.eat();
p.walk();
}
public static void method(Student s){
}
}
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
5.5.3 抽象的应用–模板方法设计模式
概念
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去(写成抽象方法),让子类去实现。
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现(重写)。这就是一种模板模式。
示例
public class TemplateMethodTest {
public static void main(String[] args) {
BankTemplateMethod btm = new DrawMoney();
btm.process();
BankTemplateMethod btm2 = new ManageMoney();
btm2.process();
}
}
abstract class BankTemplateMethod {
// 具体方法
public void takeNumber() {
System.out.println("取号排队");
}
//不确定部分(核心所在)
public abstract void transact(); // 办理具体的业务 //钩子方法
public void evaluate() {
System.out.println("反馈评分");
}
// 模板方法,把基本操作组合到一起,子类一般不能重写
public final void process() {
this.takeNumber();
this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类
//的实现代码
this.evaluate();
}
}
class DrawMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要取款!!!");
}
}
class ManageMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要理财!我这里有 2000 万美元!!");
}
}
5.6 接口
5.6.1 概念及特点
概念
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java 不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
![img](https://i-blog.csdnimg.cn/blog_migrate/da75a0ad9e05bf88c4d191d7a23de25f.png)
特点
使用
-
接口使用interface来定义。
-
在 Java 中:接口和类是并列的两个结构
-
如何去定义两个接口:定义接口中的成员
-
3.1JDK7 及以前:只能定义全局常量和抽象方法
-
全局常量:只能是public static final 的,但是书写时,可以省略不写。
-
抽象方法:只能是public abstract 的
-
-
3.2 JDK8:除了全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)。
-
-
接口中不能定义构造器!意味着接口不可以实例化。
-
Java 开发中,接口通过让类去实现(implements)的方式来使用。
-
如果实现类覆盖了接口中的所有方法,则此实现类就可以实例化。
-
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
-
Java 类可以实现多个接口 —>弥补了 Java 单继承性的局限性
格式:class AA extends BB implementd CC,DD,EE
-
接口与接口之间是继承,而且可以多继承
-
接口的具体使用,体现多态性。接口的主要用途就是被实现类实现。(面向接口编程)
-
接口,实际可以看作是一种规范
interface Flayable{
//全局变量
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1;//省略了 public static final
//抽象方法
public abstract void fly();
void stop();//省略了 public abstract
//Interfaces cannot have constructors
// public Flayable(){}
}
5.6.2 示例
/*
* 接口的使用
* 1.接口使用上也满足多态性
* 2.接口,实际上就是定义了一种规范
* 3.开发中,体会面向接口编程!
*
*/
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3 开始工作");
}
@Override
public void stop() {
System.out.println("mp3 结束工作");
}
});
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash();多态
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U 盘开始工作");
}
@Override
public void stop() {
System.out.println("U 盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
5.6.3 接口应用-代理模式
代理模式是 Java 开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();//联网前的检查工作-真实的服务器来访问网络
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器来访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
5.6.4 面试题
排错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();
}
}
排错2
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball= new Ball("PingPang"); //省略了 public static final
}
public 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"); //The final field Rollable.ball cannot be assigned.不能再被赋值
System.out.println(ball.getName());
}
}
5.6.5 Java8中关于接口的改进
Java8中除了全局常量和抽象方法之外,还可以定义静态方法、默认方法。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。
示例
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();//调用不了
// SubClass.method1();//调用不了
// 知识点 1:接口中定义的静态方法,只能通过接口来调用。
CompareA.method1();
// 知识点 2:通过实现类的对象,可以调用接口中的默认方法。
// 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
s.method2();
// 知识点 3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
// 知识点 4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
// 那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
// 这就需要我们必须在实现类中重写此方法
s.method3();
}
}
class SubClass extends SuperClass implements CompareA,CompareB{
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
// 知识点 5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3(); //调用自己定义的重写的方法
super.method3(); //调用的是父类中声明的
// 调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
public interface CompareA {
//静态方法
public static void method1() {
System.out.println("CompareA:西安");
}
//默认方法
public default void method2(){
System.out.println("CompareA:深圳");
}
default void method3(){
System.out.println("CompareA:杭州");
}
}
5.7 内部类(类的成员之五)💛
概念
-
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类.
-
内部类的分类:成员内部类(静态、非静态)VS局部内部类(方法内、代码块内、构造器内)
-
成员内部类
- 作为外部类的成员,
- 可以调用外部类的结构(静态内部类的只能调用静态方法和属性)
- 可以被static修饰
- 可以被4种不同的权限修饰
- 作为一个类,
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 可以abstract修饰
-
关注如下的3个问题
-
如何实例化成员内部类的对象
-
如何在成员内部类中区分调用外部类的结构
-
开发中局部内部类的使用 见《InnerClassTest.java》
-
示例代码
public class InnerClassTest {
public static void main(String[] args) {
//创建Dog实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类)
// Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
System.out.println();
bird.display("喜鹊");
}
}
class Person{
String name = "李雷";
int age;
public void eat(){
System.out.println("人,吃饭");
}
//静态成员内部类
static class Dog{
String name;
int age;
public void show(){
System.out.println("卡拉是条狗");
// eat();
}
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
public Bird(){
}
public void sing(){
System.out.println("我是一只猫头鹰");
Person.this.eat();//调用外部类的非静态属性
eat();
System.out.println(age);
}
public void display(String name){
System.out.println(name); //方法的形参
System.out.println(this.name); //内部类的属性
System.out.println(Person.this.name); //外部类的属性
}
}
public void method(){
//局部内部类
class AA{
}
}
//代码块中
{
//局部内部类
class BB{
}
}
//构造器中
public Person(){
//局部内部类
class CC{
}
}
}
局部内部类示例
// 返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
// 创建一个实现了Comparable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// return 0;
// }
//
// }
//
// return new MyComparable();
//方式二:匿名方式
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}