第六章 面向对象(下)
Java 面向对象学习的三条主线:
Java 类及类的成员:属性,方法,构造器,代码块,内部类
面向对象的三大特征:封装性,继承性,多态性,(抽象性)
其他关键字:this,super,static,final,abstract,interface,import等
6.1 关键字: static
6.1.1 关键字: static的理解
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才能产生出对象,这时系统才会分配内存空间给对象,其方法才会供外部空间调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每个中国人的实例对象中都单独分配一个代表国家名称的变量。
static:静态的,可以用来修饰:属性,方法,代码块,内部类
6.1.2 使用static修饰属性(静态变量或类变量)
按照是否使用static修饰,分为:静态属性和非静态属性(实例变量)
①.实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个非静态属性时,不会导致其他对象中同样的属性值的修改。
②.静态变量:我们创建了类的多个对象,多个对象共享一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,值已经被修改了。
static修饰属性的其他说明:
①.静态变量随着类的加载而加载,可以通过“类.静态变量”的方式进行调用
②.静态变量的加载要早于对象的创建
③.由于类只会加载一次,所以静态变量在内存的静态域中中也只存在一份。
④.类.静态变量,对象.静态变量,对象.实例变量
//静态变量
public class StaticTest {
public static void main(String[] args) {
Test.Id = 1;//类.静态变量
Test test = new Test();
test.Id = 2;//对象.静态变量
test.age = 23;//对象.实例变量
//Id为静态变量,被所创建的多个对象共享,因此test1对象对静态属性Id进行修改导致其他对象调用的是被修改过的
Test test1 = new Test();
test1.Id = 3;
System.out.println(test.Id);
}
}
class Test {
int age;
String name;
static int Id;
}
6.1.3 使用static修饰方法
静态方法:
①.随着类的加载而加载,可以通过“类.静态方法”的方式进行调用
②.类.静态方法,对象.非静态方法
③.静态方法中,只能调用静态的方法或属性,非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
6.1.4 使用static的注意点
①.在静态的方法内,不能使用this关键字和super关键字
②.关于静态属性和静态方法的使用,都可以从方法或属性的生命周期去理解
6.1.5 如何确定使用关键字static
①.开发中,如何确定一个属性是否要声明static关键字?
属性是可以被多个对象共享的,不会随着对象的不同而不同。
②.开发中,如何确定一个方法是否需要生命static关键字?
操作静态属性的方法,通常设置为static
工具类的方法,习惯上声明为static类型,比如:Math,Arrays等
6.1.6 static关键字的使用实例
//银行类
import java.util.Random;
public class BankMap {
Random random = new Random();
long accountNumber;
long password;
long depositBalance;
static double interestRate; //静态变量
static int minimumAmount;//静态变量
public BankMap() {
this.accountNumber = Acc();
this.password = Pas();
this.depositBalance = Dep();
}
public BankMap(long accountNumber, long password, long depositBalance) {
this.accountNumber = accountNumber;
this.password = password;
this.depositBalance = depositBalance;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
BankMap.interestRate = interestRate;
}
public static int getMinimumAmount() {
return minimumAmount;
}
public static void setMinimumAmount(int minimumAmount) {
BankMap.minimumAmount = minimumAmount;
}
//重写toString()方法
@Override
public String toString() {
return "账号:"+accountNumber+
"\n密码:"+password+
"\n账户余额:"+depositBalance+
"\n利率:"+getInterestRate()+
"\n最小额度:"+getMinimumAmount();
}
//自动生成账号
public long Acc(){
long j = 10000000000l;
for (int i = 0; i < 11; i++) {
accountNumber += random.nextInt(10) * j;
j /= 10;
}
return accountNumber;
}
//自动生成密码
public long Pas(){
int j = 100000;
for (int i = 0; i < 6; i++) {
password += random.nextInt(10) * j;
j /= 10;
}
return password;
}
//自动生成余额
public long Dep(){
int j = 10000;
for (int i = 0; i < 5; i++) {
depositBalance += random.nextInt(10) * j;
j /= 10;
}
return depositBalance;
}
}
//实现类
public class BankTest {
public static void main(String[] args) {
//给类的静态属性赋值
BankMap.setInterestRate(0.622);
BankMap.setMinimumAmount(500);
//自动生成三个账户对象信息打印输出
for (int i = 0; i < 3; i++) {
BankMap bankMap = new BankMap();
System.out.println(bankMap + "\n*********************");
}
//自定义账户信息打印输出
BankMap bankMap = new BankMap(95462328178l, 1234, 1000);
System.out.println(bankMap);
}
}
6.2 单例设计模式
6.2.1 单例设计模式的简述
设计模式是在大量的实践中总结和理论化之后优选的代码结构,编程风格,以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。“套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类的内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回内部类创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
6.2.2 单例设计模式的设计与实现
①.饿汉式 -- 单例设计模式
public class Test01 {
public static void main(String[] args) {
Play play1 = Play.getPlay();
Play play2 = Play.getPlay();
System.out.println(play1 == play2);
}
}
//饿汉式--单例设计模式
class Play{
//1.私有化类的构造器
private Play() {
}
//2.内部创建类的静态对象
private static Play play = new Play();
//3.提供公共的静态方法,返回类的对象
public static Play getPlay(){
return play;
}
}
②.懒汉式 -- 单例设计模式
public class Test01 {
public static void main(String[] args) {
Play2 play1 = Play2.getPlay();
Play2 play2 = Play2.getPlay();
System.out.println(play1 == play2);
}
}
//懒汉式 -- 单例设计模式
class Play2 {
//1.私有化类的构造器
private Play2() {
}
//2.内部创建类的静态对象
private static Play2 play = null;
//3.提供公共的静态方法,返回类的对象
public static Play2 getPlay() {
if (play == null) play = new Play2();
return play;
}
}
6.2.3 饿汉式与懒汉式的区分
饿汉式:坏处:对象加载时间过长
好处: 饿汉式是线程安全的
懒汉式:坏处:线程不安全,但可以修改(见多线程内容)
好处:延迟对象的创建
6.2.4 单例设计模式的优点
由于单例设计模式只能生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用就驻留内存的方式来解决。
举例:Java.long.Runtime
6.2.5 单例设计模式的应用场景
①.网站的计数器,一般也是单例模式实现,否则难以同步
②.应用程序的日志内容,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
③.数据库连接池的设计一般也采用单例模式,因为数据库连接是一种数据库资源
④.项目中,读取配置文件的类,一般也只有一个对象。没必要每次都使用配置文件数据,都生成一个对象去读取
⑤.Application也是单例的典型应用
⑥.Windows的Task Manager(任务管理器)就是很典型的单例模式
⑦.Windows的Recycle Bin(回收站)也是典型的单例模式的应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例
6.3 类的成员:代码块(初始化块)
6.3.1 代码块的简述
代码块用来初始化类,对象
①.普通代码块:类中方法的方法体
②.构造代码块(非静态代码块):构造块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行。
③.静态代码块:用static{ }包裹起来的代码片段,只会执行一次。静态代码块优先于构造块执行。
④.同步代码块:使用synchronized(){ }包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。同步代码块需要写在方法中。
6.3.2 静态代码块和非静态代码库(构造代码块)的区分
静态代码块:内部可以有输出语句,随着类的加载而加载,而且只执行一次,多个静态代码块的执行按照前后顺序进行执行,静态代码块优先于构造块执行,只能调用静态的属性,静态的方法,不能调用非静态结构,作用:初始化类的信息。
非静态代码块:内部可以有输出语句,随着对象的创建而执行,每创建一次对象,就执行一次非静态代码块,多个非静态代码块的执行按照前后顺序进行执行,可以调用静态的方法,属性和非静态的方法,属性,作用:可以在创建对象时,对对象的属性进行初始化。
6.3.3 代码块的使用情境
优先级:静态属性 > 静态代码块内容> 非静态代码块内容 > 方法 属性 构造器
public class Test3 {
public static String STATIC_FIELD = "静态属性";
// 静态块
static {
System.out.println(STATIC_FIELD);
System.out.println("静态代码块1");
}
public String field = "非静态属性";
// 非静态块
{
System.out.println(field);
System.out.println("非静态代码块2");
}
public InitOderTest() {
System.out.println("无参构造函数");
}
public static void main(String[] args) {
InitOderTest test = new InitOderTest();
}
// 非静态块
{
System.out.println(field);
System.out.println("非静态代码块1");
}
// 静态块
static {
System.out.println(STATIC_FIELD);
System.out.println("静态代码块2");
}
}
/*
* 运行结果 静态属性
* 静态代码块1
* 静态属性
* 静态代码块2
* 非静态属性
* 非静态代码块2
* 非静态属性
* 非静态代码块1
* 无参构造函数
*/
6.4 关键字:final
6.4.1 final(最终的)可以修饰的内容与作用
final可以来修饰的结构:类,方法,变量
①.final 用来修饰一个类:此类不能被其他类所继承
比如:String类,System类,StringBuffer类
②.final 用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass( );
③.final 用来修饰变量:此时的“变量”就称为一个常量,即不能重新赋值
④.static final 用来修饰属性:全局常量
6.4.2 final的使用案例作用
题目一:编译失败:被final修饰的变量不能再进行赋值,可以更改为 return x + 1
题目二:编译成功:fianl修饰的是对象o,而非o下的变量
6.5 抽象类与抽象方法
6.5.1 抽象类的简述
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时候一个父类设计的非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
6.5.2 abstract 关键字的使用
abstract:抽象的,可以用来修饰的结构:类,方法
abstract修饰的类即为抽象类:此类不能实例化;抽象类中一定有构造器,便于子类实例化时调用(涉及:子类实例化的全过程);开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作。
abstract修饰的方法即为抽象方法:抽象方法只有方法的声明,没有方法体;包含抽象方法的类,一定是一个抽象类,反之,抽象类中可以没有抽象方法;若子类重写了父类中的所有抽象方法后,子类方可实例化;若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要使用abstract修饰。
abstract 不能用来修饰:属性,构造器等结构;不能用来修饰私有方法,静态方法,final的方法,final的类
//抽象类1
abstract class Test01 {
//抽象方法
abstract void play();
//抽象类中可以定义非抽象方法
public void study(){
System.out.println("我爱学习哦!");
}
}
//类2
//如果类2不为抽象类的情况下,继承抽象类1需要重写实现其全部抽象类否则会报错
class Test02 extends Test01 {
//重写继承抽象类1的抽象方法
@Override
void play() {
System.out.println("123");
}
}
//实现类
class TestMain {
public static void main(String[] args) {
//创建类2对象
Test02 test02 = new Test02();
//执行类2继承的抽象类1中抽象方法的重写
test02.play();
//执行类2继承的抽象类1中的非抽象方法
test02.study();
}
}
6.5.3 抽象类的应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
//公司类
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;
}
//抽象方法
abstract void work();
}
//管理人员类
class Manager extends Employee {
private int bonus;
public Manager(int bonus) {
this.bonus = bonus;
}
public Manager(String name, int id, int salary, int bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
void work() {
System.out.println("管理员工去工作");
}
}
//员工类
class CommonEmployee extends Employee{
@Override
void work() {
System.out.println("员工在车间工作");
}
}
//实现类
class EmployeeTest{
public static void main(String[] args) {
Employee employee1 = new Manager("lisi",001,3500,1500);
employee1.work();
CommonEmployee commonEmployee = new CommonEmployee();
commonEmployee.work();
}
}
//编译结果
/*
* 管理员工去工作
* 员工在车间工作
*
* */
import java.util.Scanner;
public class SalarySystem {
public static void main(String[] args) {
Employee employee[]=new Employee[4];
Scanner scan=new Scanner(System.in);
for(int i=0;i<2;i++){
System.out.print("输入小时职员姓名:");
String name=scan.next();
System.out.print("编号:");
int number=scan.nextInt();
System.out.print("出生年、月、日:");
int year=scan.nextInt();
int month=scan.nextInt();
int day=scan.nextInt();
System.out.print("时薪:");
int wage=scan.nextInt();
System.out.print("工作时长:");
int hour=scan.nextInt();
employee[i]=new HourlyEmployee(name,number,new MyDate(year,month,day),wage,hour);
System.out.println();
}
for(int i=2;i<4;i++){
System.out.print("输入普通职员姓名:");
String name=scan.next();
System.out.print("编号:");
int number=scan.nextInt();
System.out.print("出生年、月、日:");
int year=scan.nextInt();
int month=scan.nextInt();
int day=scan.nextInt();
System.out.print("月薪:");
int monthlySalary=scan.nextInt();
employee[i]=new SalariedEmployee(name,number,new MyDate(year,month,day),monthlySalary);
System.out.println();
}
//遍历并同时为该月生日的员工增加工资
String name[]=new String[5];//用来存放当月生日员工的名单
int count=0;//遇当月生日员工个数
boolean isbirthday=false;//当月有无员工过生日
System.out.print("输入当月月份:");
int m=scan.nextInt();
System.out.println("员工类型\t\t\t\t姓名\t编号\t出生日期\t\t工资");
for(int i=0;i< employee.length ;i++){
if(m==employee[i].getBirthday() .getMonth()){
employee[i].birthdaySalaryAdd();
name[count]=employee[i].getName();
count++;
isbirthday=true;
}
System.out.println(employee[i]+"\t"+employee[i].earnings());//对象引用直接调用重写父类的toString()
}
if(isbirthday){
System.out.print("当月");
for(int i=0;i<count;i++){
System.out.print(name[i]+" ");
}
System.out.println("过生日,已为其增加工资100元");
}
}
}
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 abstract int earnings();
public abstract void birthdaySalaryAdd();//由于不同员工的工资表示形式有所差异,员工遇生日需增加100元由子类做具体实现
public String toString(){
return name+"\t"+number+"\t"+birthday.toDateString();
}
public String getName(){
return name;
}
public int getNumber(){
return number;
}
public MyDate getBirthday(){
return birthday;
}
}
class SalariedEmployee extends Employee{
private int monthlySalary;
public SalariedEmployee(String name, int number, MyDate birthday,int monthlySalary) {
super(name, number, birthday);
this.monthlySalary=monthlySalary;
}
public void birthdaySalaryAdd(){
int newSalary=monthlySalary+100;
this.setMonthlySalary(newSalary) ;
}
public void setMonthlySalary(int monthlySalary){
this.monthlySalary=monthlySalary;
}
public int earnings(){
return monthlySalary;
}
public String toString(){
return "SalariedEmployee\t"+super.toString();
}
}
class HourlyEmployee extends Employee{
private int wage;
private int hour;
private int hourlySalary;
public HourlyEmployee(String name, int number, MyDate birthday,int wage,int hour) {
super(name, number, birthday);
this.wage=wage;//每小时的工资
this.hour=hour;//月工作的总小时数
this.setHourlySalary(wage*hour);
}
public void setHourlySalary(int hourlySalary){
this.hourlySalary=hourlySalary;
}
public void birthdaySalaryAdd(){
int newSalary=hourlySalary+100;
this.setHourlySalary(newSalary) ;
}
public int earnings(){
return hourlySalary;
}
public String toString(){
return "HourlyEmployee\t\t"+super.toString();
}
}
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 String toDateString(){
return year+"年"+month+"月"+day+"日";
}
public int getMonth(){
return month;
}
}
运行结果