尚硅谷Java入门视频教程第六章——面向对象编程(下)
第6章:面向对象编程(下)
6.1 关键字:static
6.1.1 static关键字介绍
- static关键字的使用
-
static:静态的
-
static可以用来修饰:属性、方法、代码块、内部类
-
使用static修饰属性:静态变量(或类变量)
- 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
① 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
② 静态变量(类变量):我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。 - static修饰属性的其他说明:
① 静态变量随着类的加载而加载,可以通过"类.静态变量"的方式进行调用
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
④ 类/对象对变量的调用关系:类变量 实例变量 类 yes no 对象 yes yes - 静态属性举例:System.out; Math.PI;…
- 类变量 vs 实例变量内存解析
package com.atguigu.java1; 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 //编译不通过 // Chinese.name = "张继科";//不可以通过类来调用实例变量 } } //中国人 class Chinese{ String name; int age; static String nation; }
- 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
-
使用static修饰方法:静态方法
-
随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
-
类和对象对静态方法和非静态方法的调用:
静态方法 非静态方法 类 yes no 对象 yes yes -
静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
package com.atguigu.java1; public class StaticTest { public static void main(String[] args) { c1.eat(); Chinese.show(); //编译不通过//类只能调用静态的方法和属性 // Chinese.eat(); // Chinese.info(); } } //中国人 class Chinese{ String name; int age; static String nation; //非静态类 public void eat(){ System.out.println("中国人吃中餐"); //调用非静态结构 this.info(); System.out.println("name :" +name); //调用静态结构 walk(); System.out.println("nation : " + nation); } //静态类 public static void show(){ System.out.println("我是一个中国人!"); //不能调用非静态的结构 // eat(); // name = "Tom"; //可以调用静态的结构 System.out.println(Chinese.nation); walk(); } public void info(){ System.out.println("name :" + name +",age : " + age); } public static void walk(){ } }
-
-
static注意点:
- 在静态的方法内,不能使用this关键字、super关键字(此时还没有对象的创建)
- 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
-
开发中如何确定static的使用环境?
- 开发中,如何确定一个属性是否要声明为static的?
① 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
② 类中的常量也常常声明为static - 开发中,如何确定一个方法是否要声明为static的?
① 操作静态属性的方法,通常设置为static的
② 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections,在使用的时候可以直接通过类去调用,不需要再另外创建对象。
- 开发中,如何确定一个属性是否要声明为static的?
-
- static关键字应用举例:
package com.atguigu.java1; //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() ); System.out.println("c2的id:" + c2.getId() ); System.out.println("c3的id:" + c3.getId() ); System.out.println("创建的圆的个数为:" + Circle.getTotal()); } } class Circle{ private double radius; private int id;//自动赋值 public Circle(){ id = init++; total++; } public Circle(double radius){ this(); // id = init++; // total++; 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; } }
- 练习:
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。
编写主类,使用银行账户类,输入、输出3个储户的上述信息。
考虑:哪些属性可以设计成static属性
Account类:
主类:package com.atguigu.exer; public class Account { private int id; private String password = "000000"; private double balance; private static double rate; private static double minMoney = 1.0; private static int init = 1001; public Account() { super(); id = init++; } public Account(String password, double balance) { this();//复用空参构造器 this.password = password; this.balance = balance; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public static double getRate() { return rate; } public static void setRate(double rate) { Account.rate = rate; } public static void setMinMoney(double minMoney) { Account.minMoney = minMoney; } public static double getMinMoney() { return minMoney; } public int getId() { return id; } @Override public String toString() { return "Account [id=" + id + ", password=" + password + ", balance=" + balance + "]"; } }
package com.atguigu.exer; public class AccountTest { public static void main(String[] args) { Account acct1 = new Account(); Account acct2 = new Account("mima123", 2000); Account acct3 = new Account("mima456", 20000); Account.setRate(0.045); Account.setMinMoney(100); System.out.println(acct1); System.out.println(acct2); System.out.println(acct3); System.out.println(acct1.getRate()); System.out.println(acct2.getMinMoney()); } }
6.1.2 单例 (Singleton)设计模式
-
设计模式:
- 在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱——”套路”
- 总体来说设计模式分为三大类:
① 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
② 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
③ 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
-
类的单例设计模式:就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
-
单例模式的代码实现:
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。- 饿汉式:在声明对象时直接创建对象
① 直接赋值
② 使用静态代码块class Bank{ //1. 私有化类的构造器 private Bank(){ } //2. 内部创建类的对象 //4. 要求此对象也必须声明为静态的(静态方法只能调用静态属性) private static Bank instance = new Bank(); //3. 提供public、static的返回当前类对象的方法 //由于外部没有对象的创建,因此要通过类进行方法的调用 -> 此方法应该为静态的 public static Bank getInstance() { return instance; } }
③ 使用final关键字:此方法不能对生成的对象进行额外的操作class Bank{ //1. 私有化类的构造器 private Bank(){ } //2. 内部创建类的对象 //4. 要求此对象也必须声明为静态的(静态方法只能调用静态属性) private static Bank instance = null; static{ instance = new Order(); } //3. 提供public、static的返回当前类对象的方法 //由于外部没有对象的创建,因此要通过类进行方法的调用 -> 此方法应该为静态的 public static Bank getInstance() { return instance; } }
class Bank{ //1. 私有化类的构造器 private Bank(){ } //2. 内部public、static、final方法创建类的对象 public static final Bank instance = new Bank(); public static Bank getInstance() { return instance; } }
- 懒汉式(线程不安全,此写法需要修改):声明时不创建,调用方法之后才创建
class Order{ //1. 私有化类的构造器 private Order(){ } //2. 内部声明当前类对象,没有初始化 //4. 要求此对象也必须声明为静态的(静态方法只能调用静态属性) private static Order instance = null; //3. 提供公共的、静态的方法,返回类的对象 //由于外部没有对象的创建,因此要通过类进行方法的调用 -> 此方法应该为静态的 public static Order getInstance() { if(instance == null) { instance = new Order(); } return instance; } }
- 区分饿汉式 和 懒汉式
① 饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的
② 懒汉式:
好处:延迟对象的创建。
目前的写法坏处:线程不安全。—>到多线程内容时,再修改
- 饿汉式:在声明对象时直接创建对象
-
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。 -
单例模式的应用场景:
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
6.2 理解main方法的语法
- main()方法的使用说明:
- main()方法作为程序的入口
- main()方法也是一个普通的静态方法
静态的方法中不能调用非静态的方法,因此使用类中非静态的方法时必须进行对象的创建才可以调用。public class MainTest { public static void main(String[] args) {// 入口 Main.main(new String[100]); // main是静态方法,可以直接使用类调用非静态方法 test(); MainTest.test(); // main是静态方法,不可以在其中直接调用非静态方法,需要进行对象的创建 MainTest test = new MainTest(); test.show(); } // 静态方法 public static void test() { } // 非静态方法 public void show() { } } class Main { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { args[i] = "args_" + i; System.out.println(args[i]); } } }
- main()方法可以作为我们与控制台交互的方式(之前:使用Scanner)
① 使用Eclipse:package com.atguigu.java2; public class MainDemo { public static void main(String[] args) { for(int i = 0;i < args.length;i++){ System.out.println("*****" + args[i]); } } }
先执行run as java application(编译),然后执行run as run configuration
Eclipse会自动定位到当前文件,然后进行以下步骤:
输出结果:
② 使用命令行
在命令行运行时,需要将测试用例中的包名删掉,否则会报错(错误:找不到或无法加载主类)public class MainDemo { public static void main(String[] args) { for(int i = 0;i < args.length;i++){ System.out.println("*****" + args[i]); System.out.println(args[i].getClass()); int num = Integer.parseInt(args[i]); System.out.println("#####" + num); } } }
在执行时,直接在执行的命令java Xxx
后边加上要添加的参数即可,中间用空格隔开,默认也会将输入转换为String。
- 借助main方法理解面向对象:
public static void main(String[] args){方法体}
public
-> 权限修饰符:private、缺省、protected、public -> 封装性
static
-> 修饰符:static、final、abstract、native -> 可以修饰方法
void
-> 返回值类型:无返回值 / 有返回值(必须有return)
main
-> 满足标识符命名规则、规范;见名知意
String[] args
-> 重载 VS 重写;参数的值传递机制、体现对象的多态性
方法体 -> 来体现方法的具体功能
6.3 类的成员之四:代码块
-
代码块的作用:用来初始化类、对象
-
代码块如果有修饰符的话,只能使用static
-
分类:静态代码块 vs 非静态代码块
-
静态代码块
- 内部可以有输出语句
- 随着类的加载而执行,而且只执行一次
- 作用:初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行要优先于非静态代码块的执行
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
-
非静态代码块
- 内部可以有输出语句
- 随着对象的创建而执行
- 每创建一个对象,就执行一次非静态代码块
- 作用:可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
package com.atguigu.java3; public class BlockTest { public static void main(String[] args) { String desc = Person.desc; System.out.println(desc); Person p1 = new Person(); Person p2 = new Person(); System.out.println(p1.age); Person.info(); } } class Person{ //属性 String name; int age; static String desc = "我是一个人"; //构造器 public Person(){ } public Person(String name,int age){ this.name = name; this.age = age; } //非static的代码块 { System.out.println("hello, block - 2"); } { System.out.println("hello, block - 1"); //调用非静态结构 age = 1; eat(); //调用静态结构 desc = "我是一个爱学习的人1"; info(); } //static的代码块 static{ System.out.println("hello,static block-2"); } static{ System.out.println("hello,static block-1"); //调用静态结构 desc = "我是一个爱学习的人"; info(); //不可以调用非静态结构 // eat(); // name = "Tom"; } //方法 public void eat(){ System.out.println("吃饭"); } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public static void info(){ System.out.println("我是一个快乐的人!"); } }
-
练习:类中各个结构的执行顺序
总结:
① 由父及子,静态先行;
② 代码块的执行要先于构造器;
③ mian函数执行前会先进行所有类的加载。package com.atguigu.java3; //总结:由父及子,静态先行 //代码块的执行要先于构造器 class Root{ static{ System.out.println("Root的静态初始化块"); } { System.out.println("Root的普通初始化块"); } public Root(){ super(); System.out.println("Root的无参数的构造器"); } } class Mid extends Root{ static{ System.out.println("Mid的静态初始化块"); } { System.out.println("Mid的普通初始化块"); } public Mid(){ super(); 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(); System.out.println(); new Leaf(); } }
执行结果:
package com.atguigu.java3; 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(); } }
执行结果:
属性赋值的先后顺序(完结)
对属性可以赋值的位置:
① 默认初始化
② 显式初始化 / ⑤在代码块中赋值
③ 构造器中初始化
④ 了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
执行的先后顺序:① - ② / ⑤ - ③ - ④
② 显式初始化 / ⑤在代码块中赋值的具体顺序根据代码中具体的书写顺序决定,先写的先执行。
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.orderId);//4
}
}
class Order{
int orderId = 3;
{
orderId = 4;
}
}
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.orderId);//3
}
}
class Order{
{
orderId = 4;
}
int orderId = 3;
}
6.4 关键字:final
final:最终的
-
final可以用来修饰的结构:类、方法、变量
-
final 用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类 -
final 用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass(); -
final 用来修饰变量:此时的"变量"就称为是一个常量
- final修饰属性(不可以只默认初始化):
可以考虑赋值的位置有:显式初始化(各个对象的值相同)、代码块中初始化(各个对象的值相同且赋值前需要进行大量操作)、构造器中初始化(各个对象的值不同) - final修饰局部变量:
尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。 - static final 用来修饰属性:全局常量
- final修饰属性(不可以只默认初始化):
package com.atguigu.java3;
public class FinalTest {
final int WIDTH = 0;
final int LEFT;
final int RIGHT;
// final int DOWN;//必须在创建对象时已知,但是方法是创建对象之后才调用的,因此不可以使用方法来赋值
{
LEFT = 1;
}
public FinalTest(){
RIGHT = 2;
}
public FinalTest(int n){
RIGHT = n;
}
// public void setDown(int down){
// this.DOWN = down;
// }
public void doWidth(){
// width = 20;
}
public void show(){
final int NUM = 10;//常量
// NUM += 20;
}
public void show(final int num){
// num = 20;//编译不通过
System.out.println(num);
}
public static void main(String[] args) {
int num = 10;
num = num + 5;
FinalTest test = new FinalTest();
// test.setDown(3);
test.show(10);
}
}
final class FinalA{
}
//class B extends FinalA{
//
//}
//class C extends String{
//
//}
class AA{
public final void show(){
}
}
class BB extends AA{
// public void show(){
//
// }
}
面试题:排错
- final修饰形参——基本数据类型
- final修饰形参——引用数据类型
6.5 抽象类与抽象方法
6.5.1 抽象类与抽象方法介绍
abstract:抽象的
-
abstract可以用来修饰的结构:类、方法
-
abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
-
abstract修饰方法:抽象方法
只定义了一种功能的标准,具体的执行需要子类去实现- 抽象方法只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
- 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
package com.atguigu.java; public class AbstractTest { public static void main(String[] args) { //一旦Person类抽象了,就不可实例化 // Person p1 = new Person(); // p1.eat(); } } abstract class Creature{ public abstract void breath(); } abstract class Person extends Creature{ String name; int age; public Person(){ } public Person(String name,int age){ this.name = name; this.age = age; } //不是抽象方法: // public void eat(){ // // } //抽象方法 public abstract void eat(); public void walk(){ System.out.println("人走路"); } } class Student extends Person{ public Student(String name,int age){ super(name,age); } public Student(){ } public void eat(){ System.out.println("学生多吃有营养的食物"); } @Override public void breath() { System.out.println("学生应该呼吸新鲜的没有雾霾的空气"); } }
-
注意点:
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法(静态方法是通过类来进行调用的,但abstract方法中没有方法体,此时用类调用没有意义)、final的方法、final的类
-
例题
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。
对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问package com.atguigu.exer1; public abstract class Employee { private String name; private int id; private double salary; public Employee() { super(); } public Employee(String name, int id, double salary) { super(); this.name = name; this.id = id; this.salary = salary; } public abstract void work(); }
package com.atguigu.exer1; /* * 对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。 */ public class Manager extends Employee{ private double bonus;//奖金 public Manager(double bonus) { super(); this.bonus = bonus; } public Manager(String name, int id, double salary, double bonus) { super(name, id, salary); this.bonus = bonus; } @Override public void work() { System.out.println("管理员工,提供公司运行的效率"); } }
package com.atguigu.exer1; public class CommonEmployee extends Employee { @Override public void work() { System.out.println("员工在一线车间生产产品"); } }
package com.atguigu.exer1; /* * 请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问。 */ public class EmployeeTest { public static void main(String[] args) { //多态 Employee manager = new Manager("库克", 1001, 5000, 50000); manager.work(); CommonEmployee commonEmployee = new CommonEmployee(); commonEmployee.work(); } }
-
抽象类的匿名子类
不另外对抽象类创建子类进行继承,而是在使用时直接对抽象类中的方法进行重写package com.atguigu.java; /* * 抽象类的匿名子类 * */ 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.breath(); } public static void method(Student s){ } } class Worker extends Person{ @Override public void eat() { } @Override public void breath() { } }
6.5.2 抽象类的应用:模板方法设计模式(TemplateMethod)
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
- 解决的问题:
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式package com.atguigu.java; //抽象类的应用:模板方法的设计模式 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万美元!!"); } }
6.6 接口(interface)
6.6.1 接口(interface)介绍
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
- 接口的使用:
-
接口使用interface来定义
-
Java中,接口和类是并列的两个结构
-
如何定义接口:定义接口中的成员
-
JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final 的,但是书写时,可以省略不写
抽象方法:public abstract 的 -
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
-
-
接口中不能定义构造器的!意味着接口不可以实例化
-
Java开发中,接口通过让类去实现 (implements) 的方式来使用
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类 -
Java类可以实现多个接口 —>弥补了Java单继承性的局限性
格式:class AA extends BB implements CC,DD,EE -
接口与接口之间可以继承,而且可以多继承
package com.atguigu.java1; public class InterfaceTest { public static void main(String[] args) { System.out.println(Flyable.MAX_SPEED); System.out.println(Flyable.MIN_SPEED); // Flyable.MIN_SPEED = 2; Plane plane = new Plane(); plane.fly(); } } interface Flyable{ //全局常量 public static final int MAX_SPEED = 7900;//第一宇宙速度 int MIN_SPEED = 1;//省略了public static final //抽象方法 public abstract void fly(); //省略了public abstract void stop(); //Interfaces cannot have constructors // public Flyable(){ // // } } interface Attackable{ void attack(); } class Plane implements Flyable{ @Override public void fly() { System.out.println("通过引擎起飞"); } @Override public void stop() { System.out.println("驾驶员减速停止"); } } abstract class Kite implements Flyable{ @Override public void fly() { } } class Bullet extends Object implements Flyable,Attackable,CC{ @Override public void attack() { // TODO Auto-generated method stub } @Override public void fly() { // TODO Auto-generated method stub } @Override public void stop() { // TODO Auto-generated method stub } @Override public void method1() { // TODO Auto-generated method stub } @Override public void method2() { // TODO Auto-generated method stub } } //************************************ interface AA{ void method1(); } interface BB{ void method2(); } interface CC extends AA,BB{ }
-
接口的具体使用,体现多态性
-
接口,实际上可以看做是一种规范
-
面向接口编程:我们在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个数据库厂商的API
-
接口也可以创建匿名的
package com.atguigu.java1; /* * 接口的使用 * 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("打印机结束工作"); } }
-
面试题:
- 判断
- 接口能否继承接口?
可以 - 抽象类是否可以实现接口?
可以,一个类实现接口时, 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类,即抽象类是否可以实现接口。 - 抽象类是否能继承非抽象类?
可以,一个抽象类如果没有说明父类,则继承于Object类,Object类就是一个非抽象类。 - 非抽象类是否可以继承抽象类?
当然可以,否则抽象类没有存在的意义,永远无法被实例化。
- 接口能否继承接口?
- 抽象类与接口有哪些异同?
- 相同点:不能实例化;都可以包含抽象方法
- 不同点:
① 把抽象类和接口(java7、java8、java9)的定义、内部结构解释说明
② 类:单继承性;接口:多继承
类与接口:多实现
6.6.2 接口的应用:设计模式之代理模式(Proxy)与工厂模式
-
代理模式(Proxy)
-
概述:
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。 -
代码Demo1:
package com.atguigu.java1; /* * 接口的应用:代理模式 * */ public class NetWorkTest { public static void main(String[] args) { Server server = new Server(); // server.browse(); 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(); } }
代码Demo2:
package com.atguigu.java1; public class StaticProxyTest { public static void main(String[] args) { Proxy s = new Proxy(new RealStar()); s.confer(); s.signContract(); s.bookTicket(); s.sing(); s.collectMoney(); } } interface Star { void confer();// 面谈 void signContract();// 签合同 void bookTicket();// 订票 void sing();// 唱歌 void collectMoney();// 收钱 } //被代理类 class RealStar implements Star { public void confer() { } public void signContract() { } public void bookTicket() { } public void sing() { System.out.println("明星:歌唱~~~"); } public void collectMoney() { } } //代理类 class Proxy implements Star { private Star real; public Proxy(Star real) { this.real = real; } public void confer() { System.out.println("经纪人面谈"); } public void signContract() { System.out.println("经纪人签合同"); } public void bookTicket() { System.out.println("经纪人订票"); } public void sing() { real.sing(); } public void collectMoney() { System.out.println("经纪人收钱"); } }
-
应用场景:
① 安全代理:屏蔽对真实角色的直接访问。
② 远程代理:通过代理类处理远程方法调用(RMI)
③ 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。 -
分类
① 静态代理(静态定义代理类)
② 动态代理(动态生成代理类):JDK自带的动态代理,需要反射等知识
-
-
工厂模式(具体见 拓展:工厂设计模式.pdf)
- 工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
- 工厂模式的分类:
- 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
面试笔试题:排错
- 当类和接口中的属性重名时,在子类中无法直接进行识别
package com.atguigu.java1; 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(); } }
- 接口中的属性是全局常量,默认是public static final的
package com.atguigu.java1; interface 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");//final的不可以再修改 System.out.println(ball.getName()); } }
6.6.3 JDK8中接口新特性
-
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
-
知识点:
- 接口中定义的静态方法,只能通过接口来调用。
- 通过实现类的对象,可以调用接口中的默认方法。
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法 - 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>类优先原则
- 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。–>接口冲突。
这就需要我们必须在实现类中重写此方法 - 如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
子类重写的方法:method();
父类中声明的方法:super.method();
接口中声明的默认方法:接口名.super.method();
接口1:
package com.atguigu.java8; 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:上海"); } }
接口2:
package com.atguigu.java8; public interface CompareB { default void method3(){ System.out.println("CompareB:上海"); } }
父类:
package com.atguigu.java8; public class SuperClass { public void method3(){ System.out.println("SuperClass:北京"); } }
子类以及测试类:
package com.atguigu.java8; 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(); } }
6.7 类的内部成员之五:内部类
-
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
-
内部类的分类:
成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内) -
成员内部类:
-
一方面,作为外部类的成员:
① 调用外部类的结构
② 可以被static修饰
③ 可以被4种不同的权限修饰 -
另一方面,作为一个类:
① 类内可以定义属性、方法、构造器等
② 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
③ 可以被abstract修饰 -
关注如下的3个问题
- 如何实例化成员内部类的对象
① 静态的成员内部类(可直接使用类进行调用):外部类.内部类 变量名 = new 外部类.内部类();
② 非静态的成员内部类(必须使用对象调用):外部类.内部类 变量名 = (new 外部类()).new 内部类();
- 如何在成员内部类中区分调用外部类的结构:
外部类.this.属性/方法
,若属性/方法没有重名,也可以直接使用属性名/方法名
来进行调用
package com.atguigu.java2; 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{ } } }
- 如何实例化成员内部类的对象
-
-
局部内部类:外部类方法中定义的类
- 开发中局部内部类的使用:通常用来返回一个实现了某个接口的类的对象
package com.atguigu.java2; public class InnerClassTest1 { //开发中很少见 public void method(){ //局部内部类 class AA{ } } //返回一个实现了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; } }; } }
- 局部内部类使用的注意点:
在局部内部类的方法中(比如:show),如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,要求此局部变量声明为final的。
jdk 7及之前版本:要求此局部变量显式的声明为final的
jdk 8及之后的版本:可以省略final的声明
(内部类也会生成自己的class文件,在此类的方法中调用声明此局部类外部类的方法中声明的局部变量时,变量相当于复制品,不可以再进行修改)package com.atguigu.java; public class InnerClassTest { public void method(){ //局部变量 int num = 10; class AA{ public void show(){ // num = 20;//编译错误,num是final的 System.out.println(num); } } } }
- 开发中局部内部类的使用:通常用来返回一个实现了某个接口的类的对象