Java基础学习——第六章 面向对象编程(下)
Java基础学习——第六章 面向对象编程(下)
一、关键词:static
1. static关键字的引入
- 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会实例化对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用
- 但有时也希望无论是否产生了对象或无论产生了多少对象,某些特定的结构(属性、 方法、 代码块、 内部类)在内存空间里只有一份,即所有类的对象都共享这些结构,不必在每一个实例对象中单独分配**==
- 此时就需要引入static关键字。static修饰的类成员不归具体的某个对象所有,而是被所有类的对象所共享
2. static关键字的使用
- static表示静态的,可以用来修饰类的成员:属性、 方法、 代码块、 内部类。不能用来修饰构造器!
- static修饰的成员具备以下特点:
- 随着类的加载而加载,且只加载一次
- 优先于对象存在(不依赖于类的对象)
- static修饰的成员,被所有类的对象所共享
- 在访问权限允许的条件下,static修饰的成员不需要实例化对象,即可直接被类调用(通过类名来访问)
2.1 static修饰属性(静态变量、类变量)
- 属性(成员变量),按是否使用static修饰,可以分为:类变量(静态变量) vs 实例变量(非静态变量)
- 实例变量(非静态变量):类的每个对象都拥有一套独立的非静态属性,存储在不同的内存空间中,不能被类的不同对象所共享。当修改其中一个对象的非静态属性时,不会影响其他对象中的非静态属性
- 类变量(静态变量):类的所有对象都共享同一个静态属性。当通过某一个对象或直接通过类名去访问并修改该静态属性时,再通过其他对象访问该静态属性,其值也发生了变化
类变量(静态变量) | 实例变量(非静态变量) | |
---|---|---|
通过==类名==访问 | yes | no |
通过==对象==访问 | yes | yes |
- 类变量(静态变量)随着类的加载而加载,优先于对象存在。由于类只加载一次,所以静态变量在内存中(方法区的静态域)也只会存在一份,被所有类的对象所共享。可以通过 “类名.静态变量” 的方式访问,当然也可以通过对象来访问静态变量
- 类变量 vs 实例变量内存解析:
public class Day14_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";//Chinese.age = 32 //无法通过类名来访问非静态变量,因为每个对象都拥有独立的非静态变量,不能共享}
}class Chinese {String name;int age;static String nation; //静态属性
}
2.2 static修饰方法(静态方法、类方法)
- 类方法(静态方法)随着类的加载而加载,可以通过 “类名.静态方法” 的方式调用,也可以通过对象来调用
类方法(静态方法) | 实例方法(非静态方法) | |
---|---|---|
通过==类名==调用 | yes | no |
通过==对象==调用 | yes | yes |
public class Day14_StaticTest {public static void main(String[] args) {//通过类名来调用静态方法Chinese.show();Chinese c1 = new Chinese();//通过类的对象调用静态方法c1.show();//通过类的对象调用非静态方法c1.eat();//无法通过类名来调用非静态方法Chinese.eat();}
}class Chinese {//非静态方法public void eat() {System.out.println("吃")}//静态方法public static void show() {System.out.println("中国")}
}
- **静态方法中,只能调用当前类的static方法和属性(相当于前面省略了一个类名),不能调用非static的方法和属性(生命周期不够);**非静态方法中,既可以调用static的方法和属性,也可以调用非static的方法和属性
- 在静态方法内,不能使用this、super关键字。因为this表示当前类的对象,super表示父类的对象,而静态方法是不依赖于类的对象的(不需要实例化对象就可以调用)
2.3 如何判断属性或方法是否要声明为static?
- 可以被同一类的不同对象所共享,不会随着对象的不同而改变的属性,通常声明为static
- 类中的常量,如Math.PI,也通常声明为static
- 操作static属性的方法(如getter setter 方法),通常声明为static的
- Math、Arrays、Collections等工具类中的方法,通常声明为static的,可以直接通过类名来调用
3. static练习
例1 Circle类
public class CircleTest {public static void main(String[] args) {Circle c1 = new Circle();Circle c2 = new Circle();System.out.println(c1.getId()); //1001System.out.println(c2.getId()); //1002System.out.println(Circle.getTotal()); //2 static方法直接通过类名来调用}
}class Circle {private double radius; //半径private int id; //编号private static int total; //创建的Circle对象的个数:staticprivate static int init = 1001; //static属性被所有对象共享//构造器public Circle() {id = init++; //Circle对象的编号id自动增加total++; //创建的Circle对象的个数自动增加}public Circle(double radius) {this(); //调用了重载的空参构造器,相当于替换了下面两行//id = init++; //Circle对象的编号id自动增加//total++; //创建的Circle对象的个数自动增加this.radius = radius;}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}public int getId() {return id;}//static属性的getter、setter方法也会自动声明为static的public static int getTotal() {return total;}public double findArea() {return Math.PI * radius * radius;}
}
例2 银行账户
编写一个银行账户类,属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。 账号要自动生成。编写测试类,使用银行账户类,输入、输出储户的上述信息。考虑:哪些属性可以声明为static?
public class Day14_AccountTest {public static void main(String[] args) {Account2 acct1 = new Account2();Account2 acct2 = new Account2("123456", 2000);System.out.println(acct1); //自动调用了重写的toString方法System.out.println(acct2);}
}class Account2 {private int id; //idprivate String pwd;private double balance;private static double interestRate;private static double minBalance = 1.0;private static int init;//构造器public Account2() {id = init++; //id自动生成}public Account2(String pwd, double balance) {this(); //调用重载的空参构造器this.pwd = pwd;this.balance = balance;}public int getId() {return id;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}public double getBalance() {return balance;}public static double getInterestRate() {return interestRate;}public static void setInterestRate(double interestRate) {//静态方法中不能使用this,interestRate为静态属性,直接用类名调用Account2.interestRate = interestRate;}public static double getMinBalance() {return minBalance;}public static void setMinBalance(double minBalance) {Account2.minBalance = minBalance;}@Overridepublic String toString() {return "Account2{" +"id=" + id +", pwd='" + pwd + '\'' +", balance=" + balance +'}';}
}
4. 单例设计模式
4.1 设计模式
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。 设计模免去我们自己再思考和摸索。
- 23种设计模式:
- 创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
- 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
4.2 类的单例设计模式
- 对某个类,只允许在类的内部存在一个对象实例;且该类只提供一个公共的静态的方法,返回唯一的对象实例
- 实现步骤:
- 将类的构造器声明为private,避免在类的外部调用构造器创建(new)该类的对象
- 在类的内部调用该私有构造器创建该类的对象,也声明为private
- 由于在类的外部无法创建类的对象,只能调用该类的某个==公共的静态方法以返回类内部创建的对象;==静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的
- 实现方式:饿汉式 vs 懒汉式
4.3 单例模式的饿汉式实现
public class Day14_SingletonTest1 {public static void main(String[] args) {//在类的外部调用该类的公共的静态方法,返回类内部创建的唯一的对象SingletonBank bank1 = SingletonBank.getInstance();SingletonBank bank2 = SingletonBank.getInstance();System.out.println(bank1 == bank2); //true 二者共同指向类内部创建的唯一的对象}
}//饿汉式1:一般的写法
class SingletonBank {//1.类的构造器声明为private,避免在类的外部调用构造器new新的对象private SingletonBank() {}//2.在类的内部调用该私有构造器创建该类的对象,也声明为private(饿汉式在调用方法前就把对象new好了)//4.由于静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的private static SingletonBank instance = new SingletonBank();//3.提供一个公共的静态的方法,返回类内部创建的唯一的对象实例public static SingletonBank getInstance() {return instance;}
}//饿汉式2:使用静态代码块
class SingletonBank {//1.类的构造器声明为private,避免在类的外部调用构造器new新的对象private SingletonBank() {}//2.在类的内部声明一个当前类对象的引用(private),然后在静态代码块中为其new一个类的对象//4.由于静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的private static SingletonBank instance = null;//由于静态代码块随着类的加载而执行,在方法调用前对象就存在了,因为该写法是饿汉式static{instance = new SingletonOrder();}//3.提供一个公共的静态的方法,返回类内部创建的唯一的对象实例public static SingletonBank getInstance() {return instance;}
}
4.4 单例模式的懒汉式实现
public class Day14_SingletonTest2 {public static void main(String[] args) {SingletonOrder order1 = SingletonOrder.getInstance();SingletonOrder order2 = SingletonOrder.getInstance();System.out.println(order1 == order2);}
}class SingletonOrder {//1.类的构造器声明为private,避免在类的外部调用构造器new新的对象private SingletonOrder() {}//2.在类的内部声明一个当前类对象的引用(private),但不对其进行初始化(懒汉式只有在调用方法后才new对象)//4.由于静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的private static SingletonOrder instance = null;//3.提供一个公共的静态的方法,返回类内部创建的唯一的对象实例public static SingletonOrder getInstance() {if (instance == null) {//与饿汉式不同,懒汉式直接在方法内部创建对象,且只创建一次instance = new SingletonOrder();}return instance;}
}
4.5 饿汉式 vs 懒汉式
饿汉式 | 懒汉式 | |
---|---|---|
优点 | 线程安全的 | 只有在调用静态方法后才new对象,延迟了对象的创建 |
缺点 | 在调用静态方法前就把对象new好了,对象加载时间(生命周期)过长 | 暂时还存在线程安全问题,讲到多线程时,可修复 |
4.6 单例设计模式的应用场景
- 单例模式的优点:由于单例模式只生成一个对象实例, 减少了系统性能开销。当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。如:java.lang.Runtime类
- 应用场景:
- 网站的计数器,一般也是单例模式实现,否则难以同步
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作, 否则内容不好追加
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
- 读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取
- Application也是单例的典型应用
- Windows的**Task Manager(任务管理器)**是典型的单例模式
- Windows的**Recycle Bin(回收站)**是典型的单例应用。系统运行过程中, 回收站一直维护着仅有的一个实例
二、main()方法的使用说明
- main()方法是程序的入口,JVM运行程序的时候首先找的就是main方法,main()方法的格式是固定的:
public static void main(String[] args){}
- public: 表示程序的访问权限,表示的是任何的场合可以被引用,即其他类都可以访问这个main函数
- static: 告知编译器main方法是一个静态方法,main方法中的代码是存储在静态存储区的,即当定义了类以后这段代码就已经存在了。如果main()方法没有使用static修饰符,那么编译不会出错,但是如果试图执行该程序将会报错,提示main()方法不存在。因为包含main()的类并没有实例化(即没有这个类的对象),所以其main()方法也不会存在。而使用static修饰符则表示该方法是静态的,不需要实例化即可使用,即当Java虚拟机(JVM)加载类时,就会执行该方法。
static 修饰符能够与变量、方法一起使用,表示是"静态"的,不依赖类的对象的静态变量和静态方法能够通过类名来访问,不需要实例化一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法。静态变量与实例变量不同,实例变量总是通过对象来访问,因为它们的值在对象和对象之间有所不同。 - void: 表明main()的返回值是无类型的,即main方法是不需要返回值的
- String[] args: 字符串数组,接收来自程序执行时传进来的参数。如果是在控制台,可以通过编译执行将参数传进来,命令行如下:
javac HelloWorld.java
java HelloWorld a b c //字符串以空格的方式隔开
这样传进main函数的就是一个字符串数组,args[0]=a;args[1]=b;args[2]=c, 如果不传参数进来,args为空。
- 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public, 又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
- 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
三、类的成员之四:代码块
1. 代码块(初始化块)
- 代码块的作用:对Java类或对象进行初始化
- 代码块只能被static修饰, 称为静态代码块(static block), 没有使用static修饰的, 为非静态代码块
- 代码块的分类:静态代码块 vs 分静态代码块
2. 静态代码块
- 可以包含输出语句
- 静态代码块==随着类的加载而执行,且只执行一次==
- 通常用于初始化类的属性,即static的属性
- 静态代码块中==只能调用静态的属性和方法,不能调用非静态的属性和方法==,即无法对非静态的属性进行初始化
- 若一个类中定义了多个静态代码块,则根据声明的先后顺序依次执行
- 静态代码块的执行要优先于非静态代码块的执行
3. 非静态代码块
- 可以包含输出语句
- 非静态代码块==随着对象的创建而执行;每创建一个对象,都会执行一次。 且先于构造器执行==
- 可以在创建对象时,对对象的属性进行初始化
- 非静态代码块**既可以调用非静态的属性和方法, 还可以调用静态的属性和方法**
- 若一个类中定义了多个非静态代码块,则根据声明的先后顺序依次执行
public class Day14_BlockTest {public static void main(String[] args) {//静态代码块随着类的加载而执行,且只执行一次String desc = BlockPerson.desc; //static blockSystem.out.println(desc);//非静态代码块随着对象的创建而执行;每创建对象一个对象,都会执行一次BlockPerson p1 = new BlockPerson(); //blockBlockPerson p2 = new BlockPerson(); //block}
}class BlockPerson {//属性String name;int age;static String desc = "人";//构造器public BlockPerson() {}public BlockPerson(String name, int age) {this.name = name;this.age = age;}//静态代码块static {System.out.println("static block");desc = "好人";}//非静态代码块{System.out.println("block");}//方法public void eat() {System.out.println("吃饭");}@Overridepublic String toString() {return "BlockPerson{" +"name='" + name + '\'' +", age=" + age +'}';}
}
4. 体会代码块和构造器的加载顺序:由父及子,静态先行
例1
class Root{static{System.out.println("Root的静态初始化块"); //1}{System.out.println("Root的普通初始化块"); //4}public Root(){//隐藏了一个super();System.out.println("Root的无参数的构造器"); //5}
}
class Mid extends Root{static{System.out.println("Mid的静态初始化块"); //2}{System.out.println("Mid的普通初始化块"); //6}public Mid(){//隐藏了一个super();System.out.println("Mid的无参数的构造器"); //7}public Mid(String msg){//通过this调用同一类中重载的构造器this();System.out.println("Mid的带参数构造器,其参数值:" //8+ msg);}
}
class Leaf extends Mid{static{System.out.println("Leaf的静态初始化块"); //3}{System.out.println("Leaf的普通初始化块"); //9}public Leaf(){//通过super调用父类中有一个字符串参数的构造器super("尚硅谷");System.out.println("Leaf的构造器"); //10}
}
public class LeafTest{public static void main(String[] args){new Leaf();//new Leaf(); //如果再加上这一句,静态代码块就不用再加载了,其他的顺序不变}
}
例2
//由父及子 静态先行
class Father {static {System.out.println("11111111111"); //1}{System.out.println("22222222222"); //4}public Father() {System.out.println("33333333333"); //5}}public class Son extends Father {static {System.out.println("44444444444"); //2}{System.out.println("55555555555"); //6}public Son() {//super();System.out.println("66666666666"); //7}public static void main(String[] args) { //先加载类(由父及子 静态先行),再执行main方法,System.out.println("77777777777"); //3System.out.println("************************");new Son();System.out.println("************************");new Son();System.out.println("************************");new Father();}
}
5. 属性赋值的先后顺序(总结)
- 默认初始化
- 显式初始化(在声明属性时即赋一个初始化值)和代码块初始化,同级别下按声明先后顺序依次执行
- 构造器中初始化
- 通过"对象.方法"和"对象.属性"的方式赋值
//显式初始化和多个代码块,同级别下按声明先后顺序依次执行
public class OrderTest {public static void main(String[] args) {Order1 order1 = new Order1();System.out.println(order1.orderId); //4Order2 order2 = new Order2();System.out.println(order2.orderId); //3}
}class Order1 {int order = 3;{order = 4;}
}class Order2 {{order = 4;}int order = 3;
}
四、关键字:final
1. final关键字的使用
- 在Java中声明==类、 变量(包括成员变量和局部变量)和方法==时, 可使用关键字final来修饰,表示“最终的”
- ==final修饰的类不能被继承。==提高安全性, 提高程序的可读性。如:String、 System、 StringBuffer类
- ==final修饰的方法不能被子类重写。==如: Object类中的getClass()方法
- final修饰的变量(属性或局部变量),即称为常量。 名称大写, 且只能被赋值一次。
- final修饰的属性(成员变量)必须==在声明时显式初始化或在每个代码块或构造器==中显式赋值, 然后才能使用
- final修饰局部变量,尤其是修饰方法的形参时,表明该形参是一个常量。当调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内调用该形参,而不能再对其重新赋值
- static final 可以用来修饰属性和方法:
- 用来修饰属性:一般称为全局常量,随着类的加载而加载,可以直接通过类名调用,且是一个常量
- 用来修饰方法:随着类的加载而加载,可以直接通过类名调用,且不能被子类重写
2. final关键字【面试题】:改错
例1
- final修饰的形参一旦赋值以后,就只能调用,而不能再对其重新赋值
public class Something {public int addOne(final int x) { return ++x; //不合法:final修饰的形参一旦赋值以后,就只能调用,而不能再对其重新赋值// return x + 1; //合法}
}
例2
- 这里final修饰的形参o是一个引用类型变量,对其进行赋值以后,只要保证其保存的地址值不变即可,即只要保证不再让他指向别的对象即可。而 o.i++; 只是改变了其指向对象的属性值,并不会影响o的值,因此是合法的。而 o = new Other(); 显然是不合法的
public class Something {public static void main(String[] args) {Other o = new Other();new Something().addOne(o);}public void addOne(final Other o) {o.i++; //合法// o = new Other(); //不合法}
}class Other {public int i;
}
五、关键字:abstract(抽象类与抽象方法)
1. abstract抽象概念的引入
- 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
- 抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。换句话来说就是,抽象类只是提供一个大致的框架,而具体的实现方法由其子类提供。
2. abstract关键字的使用
- abstract表示抽象的,可以用来修饰:类、方法
- 不能用abstract修饰变量(属性和局部变量)、代码块、构造器
- 不能用abstract修饰 private方法(不能被重写)、static方法(不能被重写)、 final方法(不能被重写)、 final类(不能被继承)
2.1 abstract修饰类:抽象类
- 抽象类不能被实例化,即不能创建对象
- 抽象类中==一定有构造器,供子类对象实例化时调用==(子类对象实例化的全过程:一定会调用父类的构造器)
- 抽象类是用来被继承的。在开发中,都会提供抽象类的子类,让其子类实例化对象,完成相关操作
2.2 abstract修饰方法:抽象方法
- 抽象方法:只有方法的声明,没有具体的方法体,以分号结束
public abstract void talk();
- 包含抽象方法的类,一定是抽象类;反之,抽象类中可以没有抽象方法
- ① 抽象类的==子类必须重写父类(包括直接父类和所有间接父类)中所有的抽象方法==,并提供方法体,此时该子类才能实例化;② 若抽象类的子类没有重写父类(包括直接父类和所有间接父类)中所有的抽象方法,该子类仍为抽象类,必须用abstract修饰
3. 创建抽象类的匿名子类的对象
public class PersonTest {public static void main(String[] args) {Worker worker = new Worker();//1. 抽象类的非匿名子类的非匿名对象method(worker);//2. 抽象类的非匿名子类的匿名对象method(new Worker());//3. 抽象类的匿名子类的非匿名对象,该匿名子类的对象又通过多态的方式赋给了父类的引用//注意:匿名子类的内部({}内)需要对抽象类中的所有抽象方法进行重写Person p = new Person() {@Overridepublic void eat() {}@Overridepublic void breath() {}};method(p);//4. 抽象类的匿名子类的匿名对象method(new Person() {@Overridepublic void eat() {}@Overridepublic void breath() {}});}public static void method(Person p) {p.eat();p.breath();}
}//抽象类
abstract class Person {//抽象方法public abstract void eat();public abstract void breath();
}//抽象类的子类
class Worker extends Person {@Overridepublic void eat() {}@Overridepublic void breath() {}
}
4. 模板方法设计模式(抽象类、多态性)
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
- 解决的问题:
- 当功能内部一部分实现是确定的, 一部分实现是不确定的,可以把不确定的部分暴露出去,让子类去实现
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式
5. abstract练习题
编写工资系统, 实现不同类型员工(多态)的按月发放工资。 如果当月出现某个Employee对象的生日, 则将该雇员的工资增加100元。
- 定义一个Employee类,该类包含:
private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
abstract方法earnings();
toString()方法输出对象的name,number和birthday - MyDate类包含:
private成员变量year,month,day ;
toDateString()方法返回日期对应的字符串: xxxx年xx月xx日 - 定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括: private成员变量monthlySalary;
实现父类的抽象方法earnings(),该方法返回monthlySalary值; toString()方法输出员工类型信息及员工的name,number,birthday - 参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:
private成员变量wage和hour;
实现父类的抽象方法earnings(),该方法返回wage和hour值;
toString()方法输出员工类型信息及员工的name,number,birthday。 - 定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息
import java.util.Scanner;public class PayrollSystem {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.print("请输入当前月份:");int month = scan.nextInt();//虽然Employee是抽象类,不能实例化对象,但是这里只是创建了一个Employee类型的对象数组,//数组的元素是Employee类型的引用,可以通过对象的多态性指向子类的对象Employee[] emps = new Employee[2];emps[0] = new SalariedEmployee("马森", 1002, new MyDate(1991, 1, 27), 10000); emps[1] = new HourlyEmployee("潘雨生", 2001, new MyDate(1998, 9, 12), 60, 240);for (int i = 0; i < emps.length; i++) {System.out.println(emps[i]); //自动调用了子类重写的toString方法//抽象类的多态情况,编译时调用的是抽象父类中的抽象方法,运行时实际执行的是子类重写父类的方法System.out.println("月工资" + emps[i].earnings());if (month == emps[i].getBirthday().getMonth()) {System.out.println("增加工资");}}}
}abstract class Employee {private String name;private int number;private MyDate birthday;public Employee(String name, int number, MyDate birthday) {this.name = name;this.number = number;this.birthday = birthday;}public MyDate getBirthday() {return birthday;}//抽象方法public abstract double earnings();@Overridepublic String toString() {//由于没有在MyDate类中重写toString方法,这里要调用toDateString()方法才能正确的输出年月日信息return "name='" + name + '\'' + ", number=" + number + ", birthday=" + birthday.toDateString();}
}class MyDate {private int year;private int month;private int day;public MyDate(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}public int getMonth() {return month;}public String toDateString() {return year + "年" + month + "月" + day + "日";}
}class SalariedEmployee extends Employee {private double monthlySalary;public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {super(name, number, birthday);this.monthlySalary = monthlySalary;}@Overridepublic double earnings() {return monthlySalary;}@Overridepublic String toString() {return "SalariedEmployee{" + super.toString() + '}'; //直接调用父类中的toString方法}
}class HourlyEmployee extends Employee {private double wage;private int hour;public HourlyEmployee(String name, int number, MyDate birthday, double wage, int hour) {super(name, number, birthday);this.wage = wage;this.hour = hour;}@Overridepublic double earnings() {return wage * hour;}@Overridepublic String toString() {return "HourlyEmployee{" + super.toString() + '}'; //直接调用父类中的toString方法}
}
六、关键字:interface(接口)
1. 接口的概述
- 一方面, 有时必须从几个类中派生出一个子类, 继承它们所有的属性和方法。 但是, Java不支持多重继承。 有了接口, 就可以得到“多重继承”的效果,即一个类可以实现多个接口
- 另一方面, 有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征或功能而已
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守
- 面向接口编程!
2. 接口的定义和使用
- 接口使用==interface==来定义,而类使用class来定义。接口和类是Java中两个并列的结构
- 接口的定义:定义接口中的成员
- JDK7及以前:接口中只能定义==全局常量和抽象方法==
- 全局常量:接口中的所有属性(成员变量)都默认声明为 public static final,可以省略不写
- 抽象方法:接口中的所有方法都默认声明为 public abstract,可以省略不写
- JDK8新特性:接口中除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
- JDK7及以前:接口中只能定义==全局常量和抽象方法==
- 接口中==不能定义构造器,即接口不可以实例化对象==
- 接口的主要用途:通过类(实现类)来实现(implements)接口(面向接口编程)
- ① 接口的==实现类必须实现接口中所有的抽象方法==,并提供方法体,此时该实现类才能实例化;② 若接口的实现类没有实现接口中所有的抽象方法,则该实现类仍为抽象类,必须用abstract修饰
- 一个类可以实现多个接口——弥补了Java单继承性的局限性(类:单继承,多实现)
- 类的声明格式: ① 先写extends,后写implements;② 如果该类实现了多个接口,接口之间用逗号隔开
class AA extends BB implements CC, DD, EE {}
- 接口与接口之间可以继承,而且可以多继承(接口:多继承)
interface AA extends BB, CC, DD {}
- 与继承关系类似,接口与实现类之间存在多态性
//类
public class Day15_InterfaceTest {public static void main(String[] args) {System.out.println(Flyable.MAX_SPEED);System.out.println(Flyable.MIN_SPEED);Plane plane = new Plane();plane.fly();plane.stop();}
}//接口
interface Flyable {//全局常量public static final int MAX_SPEED = 7900;int MIN_SPEED = 1; //省略了public static final//抽象方法public abstract void fly();void stop(); //省略了public abstract
}interface Attackable {void attack();
}//使用实现类来实现接口
//实现类必须实现接口中所有的抽象方法,此时该实现类才能实例化
class Plane implements Flyable {@Overridepublic void fly() {System.out.println("起飞");}@Overridepublic void stop() {System.out.println("停止");}
}//若实现类没有实现接口中所有的抽象方法,则该实现类仍为抽象类,必须用abstract修饰
abstract class Kite implements Flyable {@Overridepublic void fly() {}
}//一个类可以实现多个接口
class Bullet extends Object implements Flyable, Attackable, CC {@Overridepublic void fly() {}@Overridepublic void stop() {}@Overridepublic void attack() {}@Overridepublic void method1() {}@Overridepublic void method2() {}
}//*************** 接口的多继承 ***************
interface AA {void method1();
}interface BB {void method2();
}interface CC extends AA, BB {}
3. JDK8对接口的改进(新特性)
- 接口中除了可以定义全局常量和抽象方法外,还可以定义静态方法和默认方法
3.1. 接口中的静态方法
- 使用==static==关键字修饰,包含方法体
- 接口中声明的静态方法,只能通过接口名调用,不能通过实现类的对象调用
- 而类中声明的静态方法,可以通过类名调用,可以通过类的对象调用;可以通过子类名调用,也可以通过子类的对象调用
3.2. 接口中的默认方法
- 使用==default==关键字修饰。
- 接口中声明的默认方法,可以==通过实现类对象来调用==
- 接口中声明的默认方法,可以在实现类中重新实现(重写)。如果实现类重写了接口中的默认方法,则实现类对象实际调用的是重写后的方法
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法(接口中是默认方法),那么在子类(或实现类)没有重写该方法的情况下,默认调用的是父类中同名同参数的方法——类优先原则(只针对方法)
- 如果实现类实现了多个接口,而多个接口中恰好定义了同名同参数的默认方法,那么在实现类没有重写该方法的情况下,编译会报错——接口冲突,此时必须在实现类中重写该方法
- 如何在子类(或实现类)的方法中调用父类(或接口中)被重写的方法:
- 调用父类中被重写的方法:super.方法名()
- 调用接口中被重写的默认方法:接口名.super.方法名()
public class Day15_CompareA {public static void main(String[] args) {//接口中声明的静态方法,只能通过接口名调用,不能通过实现类的对象调用CompareA.method1();//new SubClass().method1(); //编译错误:接口中声明的静态方法,不能通过实现类的对象调用//类中声明的静态方法,可以通过类名调用,可以通过类的对象调用;可以通过子类名调用,也可以通过子类的对象调用AAA.method(); //1new AAA().method(); //1BBB.method(); //1new BBB().method(); //1//接口中的默认方法,可以通过实现类的对象来调用。而且可以在实现类中重新实现(重写)new SubClass().method2();new SubClass().method3();}
}interface CompareA {//JDK8新特性1:接口中的静态方法,可以直接通过接口名调用静态方法public static void method1() {System.out.println("CompareA:北京");}//JDK8新特性2:接口中的默认方法,使用default关键字修饰,可以通过实现类的对象来调用public default void method2() {System.out.println("CompareA:上海");}//public 可以省略default void method3() {System.out.println("CompareA:上海");}
}class SubClass extends SuperClass implements CompareA {@Overridepublic void method2() {}@Overridepublic void method3() {}public void myMethod() {//调用子类(或实现类)重写的方法method3();//调用父类中的方法super.method3();//调用接口中的默认方法(非静态)CompareA.super.method3();}
}class SuperClass {public void method3() {}
}class AAA {public static void method() {System.out.println("1");}
}class BBB extends AAA {}
3.3. 接口冲突的解决 & 类优先原则
interface Filial {// 孝顺的default void help() {System.out.println("老妈,我来救你了!");}
}interface Spoony {// 痴情的default void help() {System.out.println("媳妇, 别怕,我来了!");}
}class Father {public void help() {System.out.println("儿子,救我媳妇!");}
}//情况1:接口冲突————实现类实现了多个接口,而多个接口中恰好定义了同名同参数的默认方法
class Man implements Filial, Spoony {//对多个接口中同名同参数的方法进行重写@Overridepublic void help() {System.out.println("我该怎么办呢? ");//调用指定接口中被重写的方法:接口名.super.方法名()Filial.super.help();Spoony.super.help();}
}//情况2:子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法(接口中是默认方法)
class Man extends Father implements Filial, Spoony {//那么在子类(或实现类)没有重写该方法的情况下,默认调用的是父类中同名同参数的方法——类优先原则(只针对方法)}
}
4. 接口的多态性
由于接口不能实例化对象,接口的使用需要多态性。具体表现为==接口和实现类之间的多态性==
- 对象的多态性:父类的引用指向了子类的对象。编译时调用的是父类中的方法,运行时实际执行的是子类重写父类的方法
- 接口的多态性:接口的引用指向了实现类的对象。编译时调用的是接口中的抽象方法,运行时实际执行的是实现类重新实现的方法
public class Day15_USBTest {public static void main(String[] args) {Computer c1 = new Computer();//接口的多态性:接口的引用指向了实现类的对象;//编译时调用的是接口中的抽象方法,运行时实际执行的是实现类重新实现的方法c1.transferData(new Flash());c1.transferData(new Printer());}
}class Computer {//形参为接口的引用类型变量,由于接口无法实例化对象,体现了多态性public void transferData(USB usb) {usb.start();usb.stop();}
}//接口体现了一种规范
interface USB {void start();void stop();
}//实现类:必须对接口中的所有抽象方法进行重新实现才能实例化对象(不声明为abstract)
class Flash implements USB {@Overridepublic void start() {}@Overridepublic void stop() {}
}//实现类
class Printer implements USB {@Overridepublic void start() {}@Overridepublic void stop() {}
}
5. 创建接口的匿名实现类的对象
- 与==创建抽象类的匿名子类的对象==形式类似
public class Day15_USBTest {public static void main(String[] args) {Computer c1 = new Computer();Flash flash = new Flash();//1. 接口的非匿名实现类的非匿名对象c1.transferData(flash);//2. 接口的非匿名实现类的匿名对象c1.transferData(new Flash());//3. 接口的匿名实现类的非匿名对象,该匿名实现类的对象又通过多态的方式赋给了接口的引用//注意:匿名实现类的内部({}内)需要对接口中的所有抽象方法进行重新实现USB phone = new USB() {@Overridepublic void start() {}@Overridepublic void stop() {}};c1.transferData(phone);//接口的匿名实现类的匿名对象c1.transferData(new USB() {@Overridepublic void start() {}@Overridepublic void stop() {}});}
}class Computer {public void transferData(USB usb) {usb.start();usb.stop();}
}//接口
interface USB {void start();void stop();
}//实现类
class Flash implements USB {@Overridepublic void start() {}@Overridepublic void stop() {}
}
6. 接口的应用
6.1 代理模式
代理模式是Java开发中使用较多的一种设计模式, 代理设计就是为其他对象提供一种代理以控制对这个对象的访问
public class Day15_NetWorkTest {public static void main(String[] args) {ProxyServer proxyServer = new ProxyServer(new Server()); //多态,接口的引用指向了实现类的对象proxyServer.browse();}
}interface NetWork {public abstract void browse();
}//被代理类
class Server implements NetWork {@Overridepublic 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("联网之前的检查工作");}@Overridepublic void browse() {check();work.browse(); //虚拟方法调用}
}
6.2 工厂模式(工厂方法模式和抽象工厂模式)
7. 接口练习
例1:排错
父类和接口中声明了同名属性x,由于类和接口是并列的结构,导致编译器无法确定具体调用的是谁的属性
- 如果调用的是父类的属性,则声明为 super.x
- 如果调用的是接口的属性,则声明为 A.x
interface A {int x = 0; //全局常量,前面省略了 public static final
}class B {int x = 1;
}class C extends B implements A {public void pX() {//Reference to 'x' is ambiguous, both 'B.x' and 'A.x' match//System.out.println(x); //编译错误System.out.println(super.x); //调用父类的属性System.out.println(A.x); //调用接口的属性,静态属性可以直接通过类名来访问}public static void main(String[] args) {new C().pX();}
}
例2:排错
- 接口中的属性是全局常量,前面省略了 public static final,无法被重新赋值
interface Playable {void play();
}interface Bounceable {void play();
}interface Rollable extends Playable, Bounceable {Ball ball = new Ball("PingPang"); //全局常量,前面省略了 public static final,无法被重新赋值
}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());}
}
例3 定义一个接口实现两个对象的比较
- 定义一个接口用来实现两个对象的比较:
interface CompareObject{public int compareTo(Object o); //若返回值是0,代表相等;正数代表当前对象大;负数代表当前对象小
}
- 定义一个Circle类,声明radius属性,提供getter和setter方法
- 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小
- 定义一个测试类InterfaceTest, 创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小
- 思考:参照上述做法定义矩形类 Rectangle 和 ComparableRectangle 类,在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小。
public class InterfaceTest {public static void main(String[] args) {ComparableCircle c1 = new ComparableCircle(3.4);ComparableCircle c2 = new ComparableCircle(3.6);System.out.println(c1.compareTo(c2)); //-1}
}interface CompareObject {public int compareTo(Object o); //若返回值是0,代表相等;正数代表当前对象大;负数代表当前对象小
}class Circle3 {private double radius;public Circle3() {}public Circle3(double radius) {this.radius = radius;}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}
}class ComparableCircle extends Circle3 implements CompareObject {public ComparableCircle(double radius) {super(radius);}//类似于equals()方法的重写@Overridepublic int compareTo(Object o) {if (this == o) {return 0;}if (o instanceof CompareObject) {ComparableCircle c = (ComparableCircle) o;if (this.getRadius() > c.getRadius()) {return 1;} else if (this.getRadius() == c.getRadius()) {return 0;} else {return -1;}}throw new RuntimeException("传入的数据类型不匹配");}
}
例4:抽象类与接口的异同?
- 共同点:
- 不能实例化对象,在使用时一般都用到了对象的多态性
- 抽象类和接口内都可以定义抽象方法(抽象类不一定包含抽象方法,接口中通常都会包含抽象方法)
- 不同点:
- 抽象类可以包含构造器、抽象方法、普通方法、属性;而接口只能包含全局常量和抽象方法,JDK8.0新增了静态方法和默认方法,JDK9.0新增了私有方法。抽象类可以定义构造器,而接口中不能定义构造器
- 抽象类的使用一般通过子类来继承抽象类,而接口的使用一般通过实现类来实现接口
- 一个子类只能继承一个抽象类;而一个接口可以继承多个接口(类的单继承性,接口的多继承性)
- 二者的关系:抽象类可以实现多个接口(类与接口的多实现性)
- 抽象类常见设计模式是模板方法模式;而接口常见设计模式是代理模式、工厂方法模式和抽象工厂模式
- 抽象类实际上是作为一个模板;而接口实际是一种标准、规范,表示一种能力
七、类的成员之五:内部类
1. 内部类的引入
- Java中允许将一个类A声明在另一个类B的内部,前者(类A)称为内部类,后者(类B)称为外部类
- 内部类的分类:成员内部类 vs 局部内部类
- 成员内部类:直接声明在类的内部,又可以分为静态(static)成员内部类和非静态(非static)成员内部类
- 局部内部类:声明在方法体内、代码块内或构造器内
2. 成员内部类
- 一方面,作为外部类的成员:
- 外部类只能被2种权限修饰符(public和缺省)修饰,而成员内部类==可以被4种权限修饰符修饰==
- 成员内部类能直接==调用外部类的所有成员,包括private的结构==
- 成员内部类==可以声明为static==,但此时不能再调用外部类非static的结构,只能调用外部类static的结构
- 非static成员内部类中的成员不能声明为static的,只有在外部类或static成员内部类中才可声明static成员
- 当想要在外部类的static成员部分使用内部类时, 可以考虑内部类声明为static的
- 外部类访问成员内部类的成员,需要通过 “内部类.成员” 或 “内部类对象.成员” 的方式
- 另一方面,作为一个类:
- 可以在类内定义属性、方法、构造器等结构
- 可以声明为final的,表示该内部类不能被继承
- 可以声明为abstract的,表示该内部类不能实例化对象,但可以被其它的内部类继承
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
2.1 成员内部类的使用
关注两个问题:
- 如何实例化成员内部类的对象
- 如何在成员内部类中区分调用外部类的结构
public class Day15_InnerClassTest {public static void main(String[] args) {//*************** 如何实例化成员内部类的对象 ***************//实例化静态成员内部类的对象Person3.Dog dog = new Person3.Dog();dog.show();//实例化非静态成员内部类的对象//Person3.Bird bird = new Person3.Bird(); //错误的Person3 p = new Person3();Person3.Bird bird = p.new Bird();bird.sing();}
}class Person3 {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("鸟");eat(); //前面实际上省略了: 外部类名.this.Person3.this.eat(); //如何在成员内部类中调用外部类中的结构}//*************** 如何在成员内部类中区分调用外部类的结构 ***************public void display(String name) {System.out.println(name); //方法的形参System.out.println(this.name); //内部类的属性:this表示当前类的对象System.out.println(Person3.this.name); //外部类的属性}}
}
3. 局部内部类
- 局部内部类:声明在方法体、代码块或构造器内部
public class Day15_InnerClassTest1 {//开发中不常用public void method() {//局部内部类class AA {}}//返回了一个Comparable接口的实现类的对象(多态)public Comparable getComparable1() {//创建一个Comparable接口的实现类:局部内部类class MyComparable implements Comparable {@Overridepublic int compareTo(Object o) {return 0;}}return new MyComparable();}public Comparable getComparable2() {//匿名实现类的对象return new Comparable() {@Overridepublic int compareTo(Object o) {return 0;}};}
}{System.out.println("卡拉是条狗");//eat(); //静态成员内部类中不能调用外部类非静态的结构}}//非静态成员内部类class Bird {String name;public Bird() {}public void sing() {System.out.println("鸟");eat(); //前面实际上省略了: 外部类名.this.Person3.this.eat(); //如何在成员内部类中调用外部类中的结构}//*************** 如何在成员内部类中区分调用外部类的结构 ***************public void display(String name) {System.out.println(name); //方法的形参System.out.println(this.name); //内部类的属性:this表示当前类的对象System.out.println(Person3.this.name); //外部类的属性}}
}
Java基础学习——第六章 面向对象编程(下)相关推荐
- 《疯狂Java讲义》学习笔记 第六章 面向对象(下)
<疯狂Java讲义>学习笔记 第六章 面向对象(下) 6.1包装类 基本数据类型 包装类 byte Byte short Short int Integer long Long char ...
- Java学习笔记 六、面向对象编程中级部分
Java学习笔记 六.面向对象编程中级部分 包 包的注意事项和使用细节 访问修饰符 访问修饰符的注意事项和使用细节 面向对象编程三大特征 封装 封装的实现步骤(三步) 继承 继承的细节问题 继承的本质 ...
- 【JAVA SE】第六章 面向对象、对象和类以及封装
第六章 面向对象.对象和类以及封装 文章目录 第六章 面向对象.对象和类以及封装 一.面向对象 1.概念 2.面向对象的三大特征 二.对象和类 1.基本概念 2.Java中的对象 3.Java 中的类 ...
- 第六章 面向对象编程基础总结
目录 面向过程与面向对象的区别 一.面向对象概述 1.对象 2.类 3.面向对象程序设计的特点 (1).封装 (2).继承 (3).多态 二.类与对象 1.成员变量 2.成员方法 3.构造方法 4.局 ...
- python定义一个人类_python基础课程 第9章 面向对象编程(上) 一场人类进化史
日月灿烂,若出其中,星汉灿烂,若出其里. -- 曹操.观沧海 东汉末年 人类文明以降,总会有无数的人通过对世界的观察和思考去总结我们从哪里来,要往哪里去. 遥远的古代我们的祖先通过想象力把数字变成了符 ...
- Web前端开发精品课HTML CSS JavaScript基础教程第六章课后编程题答案
编程题: 利用这样一章学到的知识,制作如图6-10所示的表格效果,并且要求代码语义化. 用VS2013新建ASP.NET空网站,添加新项,建立HTML文件,向其加入以下代码: <!DOCTYPE ...
- Java基础编程\第四-六章(面向对象编程)
Java面向对象学习的三条主线: (第4-6章) 文章目录 面向对象的举例 属性(成员变量)和局部变量 类中方法的声明和使用 Train(圆面积) ==Train(对象数组)== ==对象数组的内存解 ...
- 《疯狂Java讲义》学习笔记 第六章 面向对象(下续)
这里## 6.6 Java 9改进的接口 6.6.1 接口的概念 6.6.2 Java 9中接口的定义 6.6.3接口的继承 ==以下代码中纯在自己的很多错误== 6.6.4使用接口 6.6.5接口和 ...
- java基础学习——Swing图形化用户界面编程
GUI概述 早期计算机系统中,电脑向用户提供的是单调.枯燥.纯字符状态的"命令行界面(CLI)".就是到现在,我们还可以依稀看到它们的身影:在Windows中开个DOS窗口,就可看 ...
最新文章
- Python使用matplotlib可视化面积图(Area Chart)、通过给坐标轴和曲线之间的区域着色可视化面积图、在面积图的指定区域添加箭头和数值标签
- linux mysql忘记root密码
- 查看无线网卡是否支持监听模式
- To rename a docker image
- android byte转16进制字符串,如何将字节数组转换为十六进制字符串,反之亦然?...
- Qt Creator连接MCU
- 国家文物局:长城沿线群众是文物保护的重要力量
- Oracle GoldenGate For Big Data - Kafka Handler
- Storm实践2-【storm实时排序TopN】 - TOP10
- Microsoft Office 2007 Beta 2 下载(含所有的CD-KEY)
- android dts播放器下载,安卓dts音效apk安装包
- 计算机相关的俚语,现代俄语计算机俚语的构成方式.pdf
- poi tl 判断空值_使用poi-tl操作word模板
- MySQL 为日期增加一个时间间隔
- 3G带动企业移动管理信息化应用 直播视频
- VBS识别网页验证码
- java在浏览器闪退_浏览器连续闪退,作者来看看错误日志
- python3.0如何画表格_怎么用python画表格?
- wgt文件怎么安装到手机_安卓wgt安装
- python 战棋游戏代码实现