static关键字
static 静态
什么是静态
主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!
static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
怎么使用static
static可以用来修饰:属性、方法、代码块、内部类
使用static修饰属性:静态变量(或类变量)
-
属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
-
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
-
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
-
静态变量说明
-
静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
-
静态变量的加载要早于对象的创建。
-
由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
-
不能通过类直接调用对象中的实例变量,因为这时候对象还没有生成。
谁来调用\调用谁 | 类变量 | 实例变量 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
内存解析
静态方法说明
在静态的方法内,不能使用this关键字、super关键字
静态方法随着类的加载而加载,可以通过类.静态方法
的方式进行调用
静态方法中,只能调用静态的方法或属性,非静态(即对象的)方法无法调用。
比如:Chinese.eat();
在静态方法中调用Chinese
类的非静态方法eat()
是无法通过编译的
谁来调用\调用谁 | 静态方法 | 非静态方法 |
---|---|---|
类.方法 | yes | no |
对象.方法 | yes | yes |
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
package com.hengxing.test;
public class StaticTest {
public static void main(String[] args) {
Chinese.walk();//类调用静态方法
// Chinese.eat();//不能调用非静态方法
//对象调用
Chinese ch = new Chinese();
ch.walk();
ch.eat();
}
}
class Chinese{
public static void walk(){
System.out.println("I am walking....");
}
public void eat() {
System.out.println("I am eating.....");
}
}
结果:
I am walking....
I am walking....
I am eating.....
关于静态属性和静态方法的使用,要从生命周期的角度去理解。
什么时候用static
开发中,如何确定一个属性是否要声明为static的?
-
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
-
类中的常量也常常声明为static
开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法,通常设置为static的
- 工具类中的方法,习惯上声明为static的。 比如:
Math、Arrays、Collections
练习
- 圆的练习
package com.hengxing.test;
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
Circle c3 = new Circle(3.45);
System.out.println("c1 id is:" + c1.getId());
System.out.println("c2 id is:" + c2.getId());
System.out.println("c3 id is:" + c3.getId());
System.out.println("c3 area is:" + c3.findArea());
System.out.println("number of circle:" + Circle.getTotal());
}
}
class Circle {
private static int total;//统计对象圆的个数
private static int init = 1001;//初始值
private double radius;
private int id;//自动赋值
public Circle() {
id = init++;
total++;
}
public Circle(double radius) {
this();
this.radius = radius;
}
//计算面积
public double findArea() {
return Math.PI * radius * radius;
}
//getters
public static int getTotal() {
return total;
}
public static int getInit() {
return init;
}
public double getRadius() {
return radius;
}
public int getId() {
return id;
}
}
-
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。编写主类,使用银行账户类,输入、输出3个储户的上述信息。
考虑:哪些属性可以设计成static属性。
public class Account { private int id; private int password; private double balance;//存款余额 private static double interestRate;//利率 private static double minMoney;//最小余额 private static int init = 1000;//自动生成的初始值 public Account() { id = init++; minMoney = 10; } public Account(int password, double balance) { this(); this.password = password; this.balance = balance; } public int getId() { return id; } public int getPassword() { return password; } public void setPassword(int password) { this.password = password; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public static double getInterestRate() { return interestRate; } public static void setInterestRate(double interestRate) { Account.interestRate = interestRate; } public static double getMinMoney() { return minMoney; } public static void setMinMoney(double minMoney) { Account.minMoney = minMoney; } @Override public String toString() { return "Account [id=" + id + ", password=" + password + ", balance=" + balance + "]"; } }
单例模式(Singleton)
Point : 必须掌握,当前阶段要能够手写饿汉式
为什么需要单例模式
由于单例模式只生成一个实例, 减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
应用场景:
-
网站的计数器,一般也是单例模式实现,否则难以同步。
-
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志
文件一直处于打开状态,因为只能有一个实例去操作, 否则内容不好追加。 -
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库
资源。 -
项目中, 读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
-
Application 也是单例的典型应用
-
Windows的Task Manager (任务管理器)就是很典型的单例模式
-
Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
饿汉式实现
package com.hengxing.test;
public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();//只能通过公共方法来获取实例
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);//true
}
}
class Bank{
//1.私有化构造方法
private Bank(){}
//2.类内部生成私有对象
//4.静态化对象
private static Bank bank = new Bank();
//3.公共方法返回对象,要想在静态方法中调用,必须是静态的
public static Bank getInstance(){
return bank;
}
}
懒汉式实现
目前阶段的实现还有待优化。请看二者对比
package com.hengxing.test;
public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();//只能通过公共方法来获取实例
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);//true
}
}
class Bank{
//1.私有化构造方法
private Bank(){}
//2.类内部生成私有对象
//4.静态化对象
private static Bank bank = null;
//3.公共方法返回对象,要想在静态方法中调用,必须是静态的
public static Bank getInstance(){
if (bank == null) {
bank = new Bank();
}
return bank;
}
}
二者的对比
饿汉式
坏处:对象加载时间过长
好处:线程安全
懒汉式(目前的写法)
好处:延迟对象创建
坏处:线程不安全,若两个线程同时在if判断中,且instance
为空,会创建两个对象。—>到 多线程 内容时再修改
Main方法
main()方法的使用说明:
-
main()方法作为程序的入口
-
main()方法也是一个普通的静态方法
-
main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
方法块(初始化块)
代码块的作用
用来初始化类
代码块如果有修饰的话,只能使用static.
分类:静态代码块 vs 非静态代码块
静态代码块
内部可以有输出语句
- 随着类的加载而执行,而且只执行一次
- 作用:初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行要优先于非静态代码块的执行
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块
内部可以有输出语句
- 随着对象的创建而执行
- 每创建一个对象,就执行一次非静态代码块
- 作用:可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
回顾属性赋值的位置
按初始化顺序排列
-
默认初始化
-
显示初始化
在代码块中赋值(此两部分按编写时先后顺序排列)
-
构造器中初始化
-
有对象后,用
Object.field
和Object.method()
等方式赋值
final关键字
final
:最终的
final
可以用来修饰的结构:类、方法、变量
-
final 用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类
-
final 用来修饰方法:表明此方法不可以被重写
比如:Object类中
getClass();
-
final 用来修饰变量:此时的"变量"就称为是一个常量
- final 修饰属性:
可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
- final 修饰局部变量:
尤其是使用final 修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。
一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
static final
用来修饰属性:全局常量
abstract关键字
abstract
抽象的
abstract
可以用来修饰的结构:类、方法
abstract修饰类:抽象类
此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
注意点
abstract
使用上的注意点:
-
abstract
不能用来修饰:属性、构造器等结构 -
abstract
不能用来修饰私有方法、静态方法、final的方法、final的类
抽象类的匿名子类
先来回顾一下匿名对象
public static void main(String[] args) {
method(new Manager());//不起名了,直接丢进去
//输出:Manager is working!
}
public static void method(Employee e){
e.work();
}
//Manager类中重写了work()方法
将其称之为非匿名类的匿名对象,非匿名类指通过Manager
类创建对象
我们更进一步,把类名也匿名了,在创建对象时,直接使用抽象的父类代替,然后把需要重写的方法重写之后,放进形参中
public static void main(String[] args) {
method(new Employee() {
@Override
public void work() {
System.out.println("临时工来工作啦!!!");
}
});
}
public static void method(Employee e){
e.work();//临时工来工作啦!!!
}
这就是匿名类的匿名对象
模板方法的设计模式
public class TemplateTest {
public static void main(String[] args) {
SubTemplate t = new SubTemplate();
t.spendTime();
}
}
abstract class Template{
//计算某段代码执行所需要花费的时间
public void spendTime(){
long start = System.currentTimeMillis();
this.code();//不确定的部分、易变的部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public abstract void code();
}
实例参考:
//抽象类的应用:模板方法的设计模式
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万美元!!");
}
}
项目练习
编写工资系统, 实现不同类型员工(多态)的按月发放工资。 如果当月出现某个
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对象的生日,还要输出增加工资信息。
提示:
//定义People类型的数组People c1[]=new People[10];
//数组元素赋值
c1[0]=new People(“John”,“0001”,20);
c1[1]=new People(“Bob”,“0002”,19);
//若People有两个子类Student和Officer, 则数组元素赋值时, 可以使父类类型的数组元素指向子类。
c1[0]=new Student(“John”,“0001”,20,85.0);
c1[1]=new Officer(“Bob”,“0002”,19,90.5);
实现:
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();
@Override
public String toString() {
return "name=" + name + ", number=" + number + ", birthday=" + birthday.toDateString();
}
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;
}
}
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 String toDateString(){
return year + "年" + month + "月" + 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 class HourlyEmployee extends Employee {
private double wage;
private int hour;
public HourlyEmployee() {}
public HourlyEmployee(double wage, int hour) {
this.wage = wage;
this.hour = hour;
}
public HourlyEmployee(String name, int number, MyDate birthday, double 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() + ", wage=" + wage + ", hour=" + hour + "]";
}
}
public class SalariedEmployee extends Employee {
private double monthlySalary;
public SalariedEmployee() {}
public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
@Override
public double earnings() {
return monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee [" + super.toString() + ", monthlySalary=" + monthlySalary + "]";
}
}
import java.util.Calendar;
import java.util.Scanner;
public class PayrollSystem {
public static void main(String[] args) {
Employee[] emps = new Employee[4];
emps[0] = new HourlyEmployee("Cloud",19,new MyDate(2000,10,26),50,240);
emps[1] = new HourlyEmployee("Tifa",17,new MyDate(2003,06,16),60,240);
emps[2] = new SalariedEmployee("Arith",19,new MyDate(2003,12,8),3000);
emps[3] = new SalariedEmployee("Barit",30,new MyDate(1987,01,23),2000);
//方式一:手动获取
// Scanner scan = new Scanner(System.in);
// int month = scan.nextInt();
//方式二:自动提取
Calendar calendar = Calendar.getInstance();
int month = calendar.get(calendar.MONTH) + 1;
for (int i = 0; i < emps.length; i++) {
if (month == emps[i].getBirthday().getMonth()) {
System.out.println("今天是" + emps[i].getName() +"的生日,工资加倍!");
System.out.println(emps[i].toString());
System.out.println("月工资为:" + (emps[i].earnings() + 100));
}else {
System.out.println(emps[i].toString());
System.out.println("月工资为:" + emps[i].earnings());
}
}
}
}
接口interface
什么是接口
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。接口是Java中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
为什么需要接口
一方面, 有时必须从几个类中派生出一个子类, 继承它们所有的属性和方法。 但是, 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
接口的匿名
-
接口使用上也满足多态性
-
接口,实际上就是定义了一种规范
在开发中,体会面向接口编程!
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();
}
新特性:默认方法与静态方法
JDK8中新增:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
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:上海");
}
}
怎么使用?
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{
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
//知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
}
}
默认方法与静态方法让接口变得更像类了
新特性的应用
可以来看一个有趣的例子,妈妈和老婆同时掉水里,你救谁?
一个人如果同时继承孝顺的和痴情的两个接口,自己却不重写,那就会出错。
但是此时你爸爸发话了:“救你老妈!”子类继承了父类的方法,类优先,所以你听你爸的。
通过接口名.super.方法名();
这种方式调用接口中的默认方法
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class Father{
public void help(){
System.out.println("儿子,救我媳妇!");
}
}
class Man extends Father implements Filial, Spoony {
@Override
public void help() {
System.out.println("我该救谁呢?");
Filial.super.help();
Spoony.super.help();
}
}
内部类
什么是内部类
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
内部类分两种:
成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
为什么需要内部类
有时候我们需要描述一个类中的属性,但这个属性有点复杂,一个变量不够描述。就需要一个类作为它的属性,单独再划分一个类出去没有必要,这只是我的一个属性,别人没有。比如,人有看书
这个爱好,但是其他任何类都不需要这个属性。
怎么使用内部类
回顾外部类的成员,它可以:
- 调用外部类结构
- 被static修饰
- 被4种不同的权限修饰
作为一个类:
- 类内可以定义属性、方法、构造器等
- 被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 被abstract修饰
如何在成员内部类中区分调用外部类的结构?
//创建实例(静态的成员内部类):
TV tv = new Person.TV();
tv.show();
//创建非静态成员的内部类
Person person = new Person();
ball ball = person.new ball();
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
}
本文参考:
Java接口(Interface)的定义和实现