第6章面向对象(下)
静态变量与实例变量的对比
static关键字的使用
- static:静态的
- static可以用来修饰:属性、方法、代码块、内部类。(一定不能修饰构造器)
- 使用static修饰属性:静态变量(或类变量)
3.1 属性:按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
3.2 static修饰属性的其他说明
①静态变量随着类的加载而加载。可以通过“类.静态变量” 的方式进行调用
②静态变量的加载要早于对象的创建。
③由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中。
④是否能调用
类变量 | 实例变量 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
注意:类不可以调用实例变量
⑤静态属性举例:System.out ; Math.PI;。对于静态变量可以通过”类.属性“ 方式调用,当然还可以new 对象去调用 。而非静态变量只能通过new 对象去调用。
public class Chinese {
public String name;
public int age;
public static String nation;
}
public class StaticTest {
public static void main(String[] args) {
//可以通过"类.属性“调用静态变量
Chinese.nation = "CHN";
Chinese c1 = new Chinese();
c1.name = "姚明";
Chinese c2 = new Chinese();
c2.name = "马龙";
c1.nation = "China";
System.out.println(c2.nation);//共享共一个静态变量
}
}
类变量 vs 实例变量内存解析:静态变量随着类的加载而加载,静态变量存放在方法区的静态域中
- 使用static修饰方法:静态方法
①随着类的加载而加载,可以通过”类.静态方法“ 的方式进行调用
②是否能被调用
静态方法 | 非静态方法 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
注意:类不可以调用非静态方法。
③静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
- static注意点
①在静态的方法内,不能使用this关键字,super关键字,(静态方法随着类的加载而加载,这个时候还没有生成对象,而this表示当前对象,怎么能使用呢)
②关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。静态属性或方法随着类的加载而加载,随着类的消亡而消亡,对象在创建过之后不再被使用时便会被垃圾回收系统自动回收。也就是说类的生命周期长,相应的静态属性和方法最先被创建,最后被销毁。
//非静态方法
public void eat(){
System.out.println("中国人吃中餐");
walk();//非静态方法可以调用静态方法
nation = "CHN";//非静态方法可以调用静态属性
name = "姚明";//非静态方法可以调用非静态属性,前面省略了"this."
sleep();//非静态方法可以调用非静态方法
}
//静态方法
public static void walk(){
System.out.println("走路");
// eat();静态方法不能调用非静态方法
// name = "张继科";静态方法不能调用非静态属性
nation = "CHINA";//可以调用静态属性,前面省略了"类.”, 即Chines.
// Chinese.nation = "CHINA";跟上面那一句一样
}
public void sleep(){
}
- 开发中,如何确定一个属性是否要声明为static的?
①如果 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。 这时候可以声明为static的
②类中的常量也常常声明为static的
- 开发中,如何确定一个方法是否要声明为static的?
①操作静态属性的方法,通常设置为static的。(getter、setter方法)
②工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections等可以直接通过"类.方法" 进行调用。
练习:
//圆这个类
public class CircleOne {
private double radius;//半径
private int id;//编号
//设置为静态的,每个元素共享,一个改变所有都变
private static int total;//记录创建的圆的个数
private static int init = 1001;//id实现递增
public CircleOne() {
id = init++;
total++;
}
public CircleOne(double radius) {
this();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
//id是通过递增实现的,所以这里只提供了get方法
public int getId() {
return id;
}
//总数也不需要设置set方法
public static int getTotal() {
return total;
}
}
//圆的测试类
public class CircleOneTest {
public static void main(String[] args) {
CircleOne circleOne1 = new CircleOne();
CircleOne circleOne2 = new CircleOne();
System.out.println("c1的id:" + circleOne1.getId());
System.out.println("c2的id:" + circleOne2.getId());
System.out.println("创建圆的个数为:" + CircleOne.getTotal());
}
}
练习:
public class Account {
private int id;//账号
private String password;//密码
private double balance;//存款余额
private static double interestRate;//利率
private static double minBalance;//最小余额
private static int init = 1001;//实现账号自增
//一般构造器中只存放非静态变量,static修饰的属性不放在构造器里
public Account() {
id = init++;
}
public Account(String password, double balance) {
this();
this.password = password;
this.balance = balance;
}
public int getId() {
return id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double getBalance() {
return balance;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
public static void setMinBalance(double minBalance) {
Account.minBalance = minBalance;
}
public static double getMinBalance() {
return minBalance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", password='" + password + '\'' +
", balance=" + balance +
'}';
}
}
public class AccountTest {
public static void main(String[] args) {
Account account1 = new Account("123456",20000.0);
Account account2 = new Account("123abc",30000.0);
Account account3 = new Account("abc123",10000.0);
//通过"类.方法"调用静态方法
Account.setInterestRate(0.0125);
Account.setMinBalance(1000.0);
System.out.println(account1);
System.out.println(account2);
System.out.println(account3);
System.out.println("利率是:" + Account.getInterestRate());
System.out.println("最低余额是:" + Account.getMinBalance());
}
}
一般构造器中只存放非静态变量,static修饰的属性不放在构造器里
调用静态属性或方法可使用"类.属性" 或 "类.方法"
单例设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单例设计模式:
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中对某个类只能存在一个对象实例。
- 如何实现?
饿汉式(记忆方法,比较饿,一上来就把对象new 好)
①私有化构造器
②内部创建类的对象
③提供公共的静态的方法,返回类的对象
④要求此对象也必须声明为静态的(**理解:**静态的属性会随着类的加载而加载,仅会加载一次,也就是只会new一个对象,也就实现了单例模式。之后只是在调用这个属性,并不会再new对象)
//饿汉式1
public class Bank {
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//静态的属性会随着类的加载而加载,仅会加载一次,也就是只会new一个对象,也就实现了单例模式。之后只是在调用这个属性,并不会再new对象
//1.私有化构造器
private Bank(){
}
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
//饿汉式2,使用了static代码块,两个用哪个都可以
public class Order {
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为静态的
private static Order order = null;
//1.私有化类的构造器
private Order(){
}
static{
order = new Order();
}
//3.提供公共的静态的方法,返回类的对象
public static Order getOrder(){
return order;
}
}
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);//true
}
}
懒汉式:(记忆方法,比较懒,什么时候用什么时候new 对象,所有一开始是null)
①私有化类的构造器
②声明当前类对象,没有初始化
③提供公共的静态的方法,返回类的对象
④此对象也必须声明为静态的
//懒汉式
public class Order {
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为静态的
private static Order order = null;
//1.私有化类的构造器
private Order(){
}
//3.提供公共的静态的方法,返回类的对象
public static Order getOrder(){
if (order == null){
order = new Order();
}
return order;
}
}
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getOrder();
Order order2 = Order.getOrder();
System.out.println(order1 == order2);//true
}
}
另一种单例模式:(不推荐)
public class SingletonTest{
public static void main(String[] args){
Bank bank1 = Bank.instance;
//Bank.instance = null;//如果不声明为final用户可以对其修改
Bank bank2 = Bank.instance;
System.out.println(bank1 == bank2);
}
}
class Bank{
private Bank(){
}
//这种必须加final修饰,且没有之前的方法好,是因为之前可以的方式可以在方法体内做一些其他操作,而这里不可以
public static final Bank instance = new Bank();
}
- 区分饿汉式和懒汉式
饿汉式:
坏处:对象加载时间过长。(一开始就加载,有可能过了很久才会用到这个对象)
好处:饿汉式是线程安全的。
懒汉式:
好处:延迟对象的创建。(什么时候用到,什么时候创建)
目前的写法的坏处:线程不安全。------> 到多线程内容时,再将其修改为安全的。
单例模式的优点:**由于只生成一个实例,减少了系统性能开销,**当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。举例:java.lang.Runtime
理解main()方法的语法
main()方法的使用说明:
-
main()方法作为程序的入口
-
main()方法也是一个普通的静态方法。也可以通过"类.方法"的方式进行调用,不过这时候是在一个程序的main方法里面调用其他程序的main方法,通过"类.方法"的方式。static修饰的静态方法只能调用静态方法和属性,所以在main()方法里调用非静态方法必须先实例化对象。
-
main()方法也可以作为我们与控制台交互的方式。(之前:使用Scanner)。这个方式了解既可。注意:在控制台输入数据的时候是在运行时输入的,不管是Eclipse方式还是命令行格式。即命令行方式下输入数据是在编译javac之后,运行时java输入的。如何将控制台获取的数据传给形参:String[] args?
运行时:java 类名 “Tom” “Jerry” “123” “true”
System.out.println(args[0])//Tom,注这时候输出的还是字符串的形式
代码块
类的成员之四:代码块(或初始化块)
- 代码块的作用:用来初始化类、对象
- 代码块如果有修饰的话,只能使用static
- 分类:静态代码块 vs 非静态代码块
- 静态代码块:
- 内部可以有输出语句
- 随着类的加载而执行,而且只执行一次(自动执行,不用调用)
- 作用:初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行优先于非静态代码块的执行(类先加载,才会创建对象)
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。
- 非静态代码块
- 内部可以有输出语句
- 随着对象的创建而**执行 ** (自动执行,不用调用)
- 每创建一个对象,就执行一次非静态代码块
- 作用:可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
实际使用中不会创建多个静态代码块或非静态代码块,没有必要,只会分别创建一个。
对属性可以赋值的位置:
①默认初始化
②显式初始化 ⑤在代码块中赋值(这里指的是非静态代码块)
③构造器中初始化
④有了对象以后,可以通过"对象.属性" 或 “对象.方法” 的方式,进行赋值
执行的先后顺序①----->② / ⑤------>③----->④
②和⑤是同级,**具体谁先谁后看哪个代码在前面,在前面的先执行,在后面的后执行,**但一般在开发中属性放前面,用到代码块的话放属性后,这时候顺序就是先执行②再执行⑤。
public int high = 180;
{
high = 185;
}
Person p1 = new Person();
System.out.println(p1.high);//185
//哪个在前就先执行哪个
{
high = 185;
}
public int high = 180;//这是在类里面,不是在方法里面,不必按照顺序写结构,但一般是属性在前,其他在后,
//但在方法中必须严格按照先定义再使用
Person p1 = new Person();
System.out.println(p1.high);//180
根据代码块的作用在我们开发中可以不用代码块,源码中会出现代码块,要知道是怎么运行的。
练习:总结:由父及子,静态先行(代码块中的执行顺序先于构造器执行)
类加载完之后,再调用构造器创建对象
类加载完成(static)-------->调用构造器创建对象----------->创建对象完成(非static)
//由父及子,静态先行
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){
/*
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:猫猫猫
Leaf的普通初始化块
Leaf的构造器
*/
new Leaf();
/*
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:猫猫猫
Leaf的普通初始化块
Leaf的构造器
*/
//new Leaf();
}
}
练习:
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");
}
//main()也是静态方法,在执行之前应该先加载类
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
*/
}
}
final关键字
final:最终的
-
final可以用来修饰的结构:类、方法、变量
-
final用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类
-
final用来修饰方法:表明此方法不可以被重写。
比如Object类中getClass();获取当前对象所属的类。自己写方法一般不用final。想重写就重写,不重写就放那。
-
final 用来修饰变量:此时"变量" 就称为是一个常量
4.1 final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化(每个构造器中都要初始化)。
默认初始化不可以对常量进行赋值,必须明确的给常量一个值,常量没有默认值。
“对象.方法” 的方式不可以对常量进行赋值,原因是构造器是创建对象的最后一道关卡,当对象创建完成后,对象的每个属性都应该有值,而此时的方法还没有别调用,自然也就不能对常量初始化。
4.2 final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋予以后,就只能在方法体内使用此形参,但不能进行重新赋值。
- static final 用来修饰属性:全局变量。
public class FinalTest {
final int WITH = 0;
final int LEFT ;
final int RIGHT;
{
LEFT = 1;
}
public FinalTest(){
RIGHT = 2;
}
//当有多个构造器时,每个构造器都要初始化常量
//可以这样想,如果每个构造器没有都初始化常量,则通过不同构造器创建对象时就会导致常量没有被初始化
public FinalTest(int n){
RIGHT = n;//这里传n的意思是,不同对象的常量不一样,每创建完一个对象以后,这个对象拥有的这个常量的值
//便不可以再被修改,每个对象的常量可以不相等。常量是指初始化后不可以被修改的。
}
}
//修饰局部变量的情况
public void show(final int NUMBER){//形参只有调用该方法时才会赋值
// NUMBER = 20;//给形参赋值之后便不可以再修改
final int NUM = 10;
// NUM = 20;//方法体内声明的局部变量,当声明为常量时,便不可以被修改。
}
public void info(){
show(10);//给形参赋值之后便不可以再修改
}
面试题:
public class Something {
public int addOne(final int x) {
return ++x;//错误,常量不可以再修改
// return x + 1;//正确,常量没有被修改
}
}
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);//相当于(new Something()).addOne(o)
}
public void addOne(final Other o) {
// o = new Other(); //这个错误,重新new ,意思就是发生变化了
o.i++;//这个正确,只有修饰的常量没有变即可,他的属性发生变化是可以的
}
}
class Other {
public int i;
}
小结:一叶知秋
public static void main(String[] args){//方法体 }
权限修饰符:private 缺省 protected public ------->体现封装性
修饰符:static \ final \ abstract \ native(调用底层代码,开发不用,会在源码中看见) 可以用来修饰方法
返回值类型:无返回值 / 有返回值 ------> return
方法名:需要满足标识符命名规则、规范:“见名知意”
形参列表:重载 vs 重写;参数的值传递机制;体现对象的多态性
方法体:来体现方法的功能
抽象类与抽象方法
abstract关键字的使用
-
abstract:抽象的
-
abstract可以用来修饰的结构:类、方法
-
abstract修饰类:抽象类
①此类不能实例化。(不能new 这个类的对象)
②抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化全过程)
③开发中都会提供抽象类的子类,让子类对象实例化,完成相关的操作
-
abstract修饰方法:抽象方法
①抽象方法只有方法的声明,没有方法体
//抽象方法,有抽象方法的类一定是抽象类 public abstract void eat();
②包含抽象方法的类,一定是一个抽象类。(因为我们不能让这个类的对象去调用这个方法,即这个类不能有对象,将其声明为抽象类即可。)。反之,抽象类中可以没有抽象方法的。
③若子类重写了父类中的所有的抽象方法后(所有抽象方法包括所有父类的(直接父类和间接父类)所有抽象方法),此子类方可实例化。
若子类没有重写父类中的所有的抽象方法(所有抽象方法包括所有父类的(直接父类和间接父类)所有抽象方法),则此子类也是一个抽象类,需要使用abstract修饰。
抽象类应用举例:
在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。问题:卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。
//Vehicle是一个抽象类,有两个抽象方法。
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
//子类重写父类的抽象方法
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 }
}
//子类重写父类的抽象方法
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法}
}
abstract使用上的注意点:
- abstract不能用来修饰:属性、构造器等结构。
- abstract不能用来修饰私有方法(私有方法不可以被重写)、静态方法(静态方法static不是重写)、final的方法、final的类(有final代表不能重写,不能继承)。
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(此时不是重写)。
练习:
public abstract class Employee {//抽象类
private String name;
private int id;
private int salary;
public Employee() {
}
public Employee(String name, int id, int salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public abstract void work();//抽象方法,具体的实现方法交给子类重写
}
public class Manager extends Employee{
private int bonus;
public Manager() {
}
public Manager(String name, int id, int salary, int bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("管理员工");
}
public int getBonus() {
return bonus;
}
public void setBonus(int bonus) {
this.bonus = bonus;
}
}
public class CommonEmployee extends Employee{
public CommonEmployee() {
}
public CommonEmployee(String name, int id, int salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("工作上班");
}
}
public class EmployeeTest {
public static void main(String[] args) {
Employee employee = new Manager();//多态实现
Manager manager = new Manager("马云",1001,20000,300000);
manager.work();
Employee employee1 = new CommonEmployee("马化腾",1001,100000);
employee1.work();
}
}
创建抽象类的匿名子类:
public class EmployeeTest {
public static void main(String[] args) {
method(new Manager());//创建非匿名子类的匿名对象。子类是Manager
Manager manager = new Manager("马云",1001,20000,300000);
method(manager);//非匿名子类的非匿名对象
Employee employee2 = new Employee() {
@Override
public void work() {
}
};//匿名子类的非匿名对象,注意这里有" ; " 号
method(new Employee() {
@Override
public void work() {
}
});//匿名子类的匿名对象
}
public static void method(Employee employee){
employee.work();
}
}
模板方法的设计模式
多态的应用:模板方法设计模式(TemplateMethod)。抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
**当功能内部一部分实现是确定的,一部分实现是不确定的。**这时可以把不确定的部分暴露出去,让子类去实现。
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
举例:
public abstract class Template {
//计算某段代码执行所花费的时间
public void spendTime(){
Long start = System.currentTimeMillis();
code();//不确定的部分、易变的部分
Long end = System.currentTimeMillis();
System.out.println("所花费的时间为:" + (end - start));
}
public abstract void code();//执行的代码,具体不确定,放到子类重写
}
public class SubTemplate extends Template{
@Override
public void code() {
//计算质数
for (int i = 2; i <= 10000; i++) {
boolean flag = true;
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0){
flag = false;
break;
}
}
if (flag){
System.out.println(i);
}
}
}
}
public class TemplateTest {
public static void main(String[] args) {
Template template = new SubTemplate();
template.spendTime();
}
}
举例2:
//抽象类的应用:模板方法的设计模式
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万美元!!");
}
}
//总而言之就是父类中声明公共的都要执行的方法,一些不确定的方法声明为abstract的,在子类中重写该方法,哪个子类调用就执行那个重写的方法
抽象类练习:
public abstract class Employee {
private String name;//姓名
private int number;//工号
private MyDate birthday;//生日
public Employee() {
}
public Employee(String name, int number, MyDate birthday) {
this.name = name;
this.number = number;
this.birthday = birthday;
}
public abstract double earnings();//挣钱
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return
"name='" + name + '\'' +
", number=" + number +
", birthday=" + birthday.toDateString(); //注意这里birthday应该调用toDateString(),
// 因为这是在MyDate类中声明的,没有重写toString方法
}
}
public class SalariedEmployee extends Employee{
private int monthlySalary;
public SalariedEmployee() {
}
public SalariedEmployee(String name, int number, MyDate birthday, int monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
@Override
public double earnings() {
return monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee{" +
super.toString() + //调用父类toString方法
'}';
}
}
public class HourlyEmployee extends Employee{
private int wage;//每小时的工资
private int hour;//工作的月小时数
public HourlyEmployee() {
}
public HourlyEmployee(String name, int number, MyDate birthday, int wage, int hour) {
super(name, number, birthday);
this.wage = wage;
this.hour = hour;
}
@Override
public double earnings() {
return wage * hour;
}
@Override
public String toString() {
return "HourlyEmployee{" +
super.toString() +
'}';
}
}
public class MyDate {
private int year;//年份
private int month;//月份
private int day;//天
public MyDate() {
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
//输出生日信息
public String toDateString(){
return year + "年" + month + "月" + day + "日";
}
}
public class PayrollSystem {
public static void main(String[] args) {
//获取当前月份方式一
/*System.out.println("请输入当前月份");
Scanner scanner = new Scanner(System.in);
int month = scanner.nextInt();//获取月份*/
//方式二
Calendar calendar = Calendar.getInstance();
int month = calendar.get(Calendar.MONTH);//只不过这里获取的月份是从0开始计数的,一月对应的是0
//所有后面代码month要加1
System.out.println(month);
Employee[] emp = new Employee[2];
emp[0] = new SalariedEmployee("马云",1001,new MyDate(2000,2,21),10000);
emp[1] = new HourlyEmployee("马化腾",1002,new MyDate(2001,1,13),60,240);
for (int i = 0; i < emp.length; i++) {
System.out.print(emp[i]);
double salary = emp[i].earnings();
System.out.println(" 月工资为:" + salary);
if (month == emp[i].getBirthday().getMonth()){
System.out.println(emp[i].getName() + "生日快乐!奖励100元");
}
}
}
}
接口的定义与使用
接口的使用
-
接口使用interface来定义
-
Java中接口和类是并列的两个结构。
-
如何定义接口:定义接口中的成员
3.1 JDK7及以前:只能定义全局常量和抽象方法。
- 全局常量:public static final 的。但是书写是可以省略不写(不写也是存在的)
- 抽象方法:public abstract 的。书写时也可以省略
3.2 JDK8:除了定义全局变量和抽象方法之外,还可以定义静态方法、默认方法
-
**接口中不能定义构造器!**意味着接口不可以实例化
-
Java开发中,接口通过让类去实现(implements)的方式来使用。
-
如果实现类覆盖(覆盖即实现,对于抽象方法我们说实现,非抽象方法说重写)了接口中的所有抽象方法,则此实现类就可以实例化
-
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
-
Java类可以实现多个接口 ----- >弥补了Java单继承性的局限性
格式: class AA extends BB implements CC,DD,EE{}
先继承父类,再实现接口,多个接口之间用逗号","分隔
//如下是String类,继承了Object类,没有显式写,还继承了多个接口 public final class String implements java.io.Serializable, Comparable<String>, CharSequence {}
-
接口与接口之间可以继承,而且可以多继承。
格式:interface AA extends BB,CC {}
这时候要实现所有接口中的所有抽象方法
-
接口的具体使用,体现多态性
-
接口实际上可以看做是一种规范
/*
接口的使用
1.接口使用上也满足多态。(声明的是接口,创建的是实现接口的类的对象)
2.接口实际上就是定义了一种规范
3.开发中体会面向接口编程!
*/
public class USBTest {
public static void main(String[] args) {
Computer computer = new Computer();
Flash flash = new Flash();
computer.transferData(flash);
}
}
class Computer{
public void transferData(USB usb){//体现多态:USB usb = new Flash();声明的是USB,但实际上new的是一个实现类的对象
usb.start();
System.out.println("具体的传输细节");
usb.stop();
}
}
interface USB{
//一些常量:如宽度,深度等
void start();//开始启动方法
void stop();//停止方法
}
class Flash implements USB{
@Override
public void start() {
System.out.println("Flash启动");
}
@Override
public void stop() {
System.out.println("Flash停止");
}
}
接口的匿名实现类:(类似于抽象类的匿名子类)
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
computer.transferData(flash);
//2.创建接口的非匿名实现类的匿名对象
computer.transferData(new Flash());
//3.创建了接口的匿名实现类的非匿名对象
USB phone = new USB() {
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
//4.创建了接口的匿名实现类的匿名对象
computer.transferData(new USB() {
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
代理模式
概述:
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
左边:代理类 右边:被代理类
举例1:
public class ProxyTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
//接口
interface Network{
void browse();
}
//被代理类
class Server implements Network{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements Network{
private Network network;
public ProxyServer(Network network) {
this.network = network;
}
public void check(){
System.out.println("检查网络状态");
}
@Override
public void browse() {
check();
network.browse();
}
}
举例2:
public class StaticProxyTest {
public static void main(String[] args) {
Star 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("经纪人收钱");
}
}
工厂设计模式
接口的应用:工厂模式 (这里只简单了解即可)
工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式实现了创建与调用分离, xxxFactory() 只用来创建对象,主方法调用对象。
以后看见 xxxFactory() 知道是专门造对象的就可以了。
面试题排错:
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
//System.out.println(x);//这句话编译不通过,编译器不知道调用的是父类的还是接口的,仅对属性而言,方法没有此
//因为接口和类是并列的,所以并没有就近这一说法
System.out.println(super.x);//输出父类结果
System.out.println(A.x);//接口中属性是全局常量,可以通过"类.属性"的方式调用,输出接口中的结果
}
public static void main(String[] args) {
new C().pX();
}
}
面试题排错:
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"); //编译错误,接口中ball是public static final 全局常量,不可以再被修改
System.out.println(ball.getName());
}
}
接口练习:(其中比较大小Compare方法要掌握)
public class CircleTwo {
private Double radius;//半径
public CircleTwo(Double radius) {
this.radius = radius;
}
public Double getRadius() {
return radius;
}
public void setRadius(Double radius) {
this.radius = radius;
}
}
public interface CompareObject {
//返回值是0,代表相等;若为正数,代表当前对象大,若为负数代表当前对象小
//接口可以理解为一种规范,都应该这么做,只是不同类型具体的实现方式不一样
public int compareTo(Object o);
}
public class ComparableCircle extends CircleTwo implements CompareObject{
public ComparableCircle(Double radius) {
super(radius);
}
@Override
public int compareTo(Object o) {
if (this == o){
return 0;
}
if (o instanceof ComparableCircle){
ComparableCircle comparableCircle = (ComparableCircle) o;
//这种方法不恰当,因为强转可能会导致损失精度如4.3 - 4.1 在强转后结果为0,显然在这个中不好
// return (int) (this.getRadius() - circleTwo.getRadius());
//正确方法方式一,这个时候radius声明为基本数据类型double
/*if (getRadius() - comparableCircle.getRadius() > 0){
return 1;
}else if (getRadius() - comparableCircle.getRadius() < 0){
return -1;
}else {
return 0;
}*/
//正确方式二,使用包装类
//当属性radius声明为Double类型时,可以调用包装类的方法
return this.getRadius().compareTo(comparableCircle.getRadius());
}
return 0;
//等学了异常就可以抛异常,对象不匹配
}
}
public class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(4.3);
ComparableCircle c2 = new ComparableCircle(4.2);
int i = c1.compareTo(c2);
if (i > 0){
System.out.println("c1对象大");
}else if (i < 0){
System.out.println("c2对象大");
}else {
System.out.println("一样大");
}
}
}
Java8中接口的新特性
记住以后见到接口中的默认方法,子类中的对象可以直接调用。(意味着java8之后接口越来越像一个类。)
JDK8:除了定义全局变量和抽象方法之外,还可以定义静态方法、默认方法静态方法、默认方法定义的格式。静态方法,默认方法也可以有方法体了,JDK7之前没有方法体。
public interface CompareA {
//静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
//将public省略掉也可以,但是方法依旧是public的
default void method3(){
System.out.println("CompareA:上海");
}
}
public class SubClassTest {
public static void main(String[] args) {
SubClass subclass = new SubClass();
// subclass.method1();
//知识点1:接口中定义的静态方法,只能通过接口来调用
CompareA.method1();
//知识点2:通过实现类的对象,可以调用接口中的默认方法
//知识点3:如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
subclass.method2();
//知识点4:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。----->类优先原则(仅对方法而言)
//对于属性没有这个原则,必须显式区分,不然不知道调用哪个,编译器会报错
//知识点5:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//那么在实现类没有重写此方法的情况下,报错。----->接口冲突。(不知道该调用哪个)
//这就需要我们必须在实现类中重写此方法
subclass.method3();
//不能通过类名调用非静态方法
// SubClass.method2();
}
}
class SubClass extends SuperClass implements CompareA{
@Override
public void method2() {
System.out.println("SubClass:上海");
}
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//重写方法时调用接口中的默认方法,第一次这么用
CompareA.super.method3();
}
}
class SuperClass{
public void method3(){
System.out.println("SuperClass:北京");
}
}
interface CompareB{
default void method3(){
System.out.println("CompareA:上海");
}
}
知识点1:接口中定义的静态方法,只能通过接口来调用.(注意与类中的静态方法做区别,类中的静态方法,对象也可以调用)
知识点2:通过实现类的对象,可以调用接口中的默认方法
知识点3:如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
知识点4:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。----->类优先原则(仅对方法而言)对于属性没有这个原则,必须显式区分,不然不知道调用哪个,编译器会报错
知识点5:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。----->接口冲突。(不知道该调用哪个)这就需要我们必须在实现类中重写此方法
重写方法时调用接口中的默认方法:
CompareA.super.method3();//接口.super.方法名
这里面讲的太细了,实际开发中不会出现这么多情况。
记住以下结论:
这一部分**只需要记住以后见到接口中的默认方法,子类中的对象可以直接调用**。(意味着java8之后接口越来越像一个类。)。实际开发中接口中的静态方法见的比较少。
内部类
类的内部成员之五:内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- Java中允许将一个类A声明在另一个类B中,则类A就是内部类、类B称为外部类
- 内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
- 成员内部类:
- 一方面,作为外部类的成员:
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
- 另一方面,作为一个类
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意不使用final就可以被继承
- 可以被abstract修饰
- 一方面,作为外部类的成员:
- 关注如下的3个问题
- 如何实例化成员内部类的对象
- 如何在成员内部类中区分调用外部类的结构
- 开发中局部内部类的使用
具体实现方法见代码:
public class InnerClassTest {
public static void main(String[] args) {
//创建Dog实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
dog.sleep();
//创建Bird实例(非静态的成员内部类)
// Person.Bird bird = new Person.Bird();//错误的
//非静态成员必须通过对象去调用,所以先创建外部类的对象
Person person = new Person();
Person.Bird bird = person.new Bird();
bird.shout();
}
}
class Person{
private String name;
public void eat(){
System.out.println("吃饭");
}
//静态成员内部类
static class Dog{
private int age;
public void sleep(){
System.out.println("狗睡觉");
}
public Dog() {
}
}
//非静态成员内部类
class Bird{
private String name;
private String kind;
public void shout(){
Person.this.name = "百灵鸟";
eat();//前面省略了"类.this" ,只有this代表当前类Bird
// Person.this.eat();
}
public Bird(){
}
public void info(String name){
//2.如何在成员内部类中区分调用外部类的结构
//当重名时如何调用指定结构,开发中会避免重名,当没有重名时直接填写属性名即可
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{
//局部内部类(构造器内)
}
}
}
局部内部类的使用:
public class InnerClassTest1 {
//局部内部类的使用
//开发中这样用也很少
public void method(){
class AA{
//局部内部类(方法内)
}
}
//返回一个实现了Comparable接口的对象
//方式一
/*public Comparable getComparable(){
class Compare implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
//非匿名实现类的匿名对象
return new Compare();
}*/
//方式二
public Comparable getComparable(){
//匿名实现类的匿名对象
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
实际开发中自己用到内部类的情况不多,更多的情况是可以知道源码中如何使用内部类即可。
总结:成员内部类和局部内部类,在编译以后,都会生成字节码文件。
格式:成员内部类: 外部类$内部类名.class
如Person$Dog.class
局部内部类:外部类$数字 内部类名.class
如Person$1AA.class
局部内部类使用的一个注意点
在局部内部类的方法中(比如show)如果调用局部内部类所声明的方法(比如method)中的局部变量(比如:num)要求此局部变量声明为final的。
-
jdk7及之前的版本,要求此局部变量显式的声明为final的
-
jdk8及以后的版本,可以省略final的声明(但依旧是final,只是省了)
public class InterfaceTest {
/*
在局部内部类的方法中(比如show)如果调用局部内部类所声明的方法(比如method)中的局部变量(比如:num)
要求此局部变量声明为final的。
jdk7及之前的版本,要求此局部变量显式的声明为final的
jdk8及以后的版本,可以省略final的声明
*/
public void method(){
//局部变量
int num = 10;//省略了final
class AA{
public void show(){
// num = 20;//常量不可以修改
System.out.println("num = " + num);
}
}
}
}
面试题:抽象类和接口有哪些共同点和区别?
相同点:不能实例化;都可以包含抽象方法的
不同点:
-
把抽象类和接口(java7,java8,java9)的定义、内部结构进行解释说明。(java9新增私有方法)
-
类:单继承性 接口: 多继承
类与接口:多实现