1. static关键字
static可以用来修饰:属性、方法、代码块、内部类
1.1 static 修饰属性
使用static
关键字修饰的变量(属性)叫静态变量(属性),与之相对的非静态变量称为实例变量(属性)。
实例变量:我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,也获取到修改过的值。
关于static
关键字修饰的静态变量的说明:
- 静态变量随着类的加载而加载
- 静态变量的加载早于对象的创建
- 由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方发区的静态域中
- 静态变量可以被对象调用,也可以被类直接调用;类不可以直接调用实例变量。
静态变量举例:System.out.print()
、Math.PI
静态变量的内存结构:
public class StaticTest {
public static void main(String[] args) {
Chinese c1 = new Chinese();
c1.name = "张三";
c1.age = 30;
Chinese c2 = new Chinese();
c2.name = "李四";
c2.age = 30;
c1.nation = "CN";
System.out.println(c2.nation);
}
}
class Chinese {
String name;
int age;
static String nation;
}
1.2 static修饰方法
- 随着类的加载而加载,可以通过
类.方法()
的方式调用 - 对象可以调用实例方法,也可以调用静态方法,而类只能直接调用静态方法。
- 静态方法中只能调用静态方法或属性,非静态方法中既可以调用静态方法或属性,也可以调用非静态方法或属性。
1.3 static关键字使用的注意点
在静态方法内,不能使用this
、super
关键字。
主要原因是因为生命周期,静态方法的生命周期是类加载时开始,而对象的声明周期是对象的创建,要短于静态方法的生命周期,调用时要保证调用的目标存在于内存中,因此静态方法中只能使用静态的方法或属性,而生命周期较短的对象可以调用静态的方法或属性。
1.4 恰当地使用static关键字。
在开发中,如何确定一个属性是否应声明为static:
- 属性可以被多个对象共享,不会随着对象的不同而不同。
在开发中,如何确定一个方法是否应声明为static:
- 操作静态属性的方法通常设置为静态
- 工具类中的方法习惯上声明为static,如
Math
、Arrays
等
1.5 static的应用——单例模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱免去我们自己再思考和摸索。
所谓单例设计模式,就是采用一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只能怪呢提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,首先必须将类的构造器访问权限设置为private
,这样,就不能用new
在外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类外部还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单例模式的饿汉式实现(类加载即创建):
public class SingletonTest {
public static void main(String[] args) {
Bank b1 = Bank.getInstance();
Bank b2 = Bank.getInstance();
System.out.println(b1 == b2);
}
}
// 希望只创建一个对象的类
class Bank {
// 1. 私有化构造器
private Bank() {
// 初始化操作
}
// 2. 内部创建类的对象(必须是静态)
private static Bank instance = new Bank();
// 3. 提供公共的方法返回类的对象
public static Bank getInstance() {
return instance;
}
}
运行结果:true,说明两个对象的地址值相同,符合单例模式的要求
单例模式的懒汉式实现(用的时候再创建):
/*
* 单例模式的懒汉式实现
* */
public class SingletonTest2 {
public static void main(String[] args) {
Order o1 = Order.getInstance();
Order o2 = Order.getInstance();
System.out.println(o1 == o2);
}
}
class Order {
// 1. 私有化类的构造器
private Order() {
}
// 2. 声明当前类对象,没有初始化
private static Order instance = null;
// 3. 声明public static方法返回对象
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
运行结果:true
饿汉式和懒汉式的区别:
- 饿汉式:天然线程安全,但是会加长对象加载的时间
- 懒汉式:延迟对象创建,降低类加载时间,但是上文的写法存在线程安全问题,例如两个线程几乎同时获取对象,则容易产生两个对象。修改方式在多线程内容时进行描述。
单例模式的使用场景:
- 网站的计数器:一般使用单例模式,否则难以实现同步
- 应用程序的日志:一般都使用单例模式,因为共享的日志文件一直处于打开状态,只能有一个实例对其进行操作
- 数据库连接池:因为数据库连接是一种数据库资源
- 项目中读取配置文件的类:因为没有必要每次读取配置文件时都专门生成一个对象去取
- Application类也是单例的典型应用
- Windows的Task Manager(任务管理器)
- Windows的Recycle Bin(回收站)
2. 代码块的使用(初始化块)
- 代码块的作用:用来初始化类或对象
- 只能使用
static
修饰 分类:静态代码块以及非静态代码块
静态代码块特点:
- 内部可以有输出语句
- 随着类的加载而执行,且只执行一次
- 如果一个类中声明了多个静态代码块,则按照声明顺序依次执行
- 静态代码块的执行优先于动态代码块
非静态代码块特点:
- 内部可以有输出语句
- 随着对象的创建而执行,每创建一个对象就执行一次
- 作用:在创建对象时为对象的属性进行初始化
- 如果一个类中声明了多个非静态代码块,则按照声明顺序依次执行
- 非静态代码块可以调用静态属性、静态方法,或非静态的属性、非静态的方法。
- 代码块的执行要先于构造器,各个部分代码执行的顺序:由父及子,静态先行
属性可以赋值的位置(按执行先后顺序):
- 默认初始化
- 显式初始化、代码块中初始化
- 构造器中初始化
- 创建对象后通过对象赋值
3. final关键字
final
可以用来修饰的结构:- 类
- 方法
- 变量
final
用来修饰一个类:此类不能被其它类所继承,例如String
、System
、StringBuffer
等final
用来修饰一个方法,表明此方法不可被重写,例如Object.getClass()
final
用来修饰变量,此时的“变量”就称为是一个常量- 修饰属性时,可以考虑赋值的位置有:显示初始化、代码块中以及构造器中初始化
- 修饰局部变量时,尤其是修饰形参,表明此形参是一个常量,当我们调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内使用此形参,但不能重新赋值。
static final
用来修饰全局常量
4. 抽象类与抽象方法
4.1 abstract关键字的使用
- abstract关键字可以用来修饰类和方法。
修饰类时:
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类完成实例化,完成相关操作
修饰方法时:
- 抽象方法只有方法的声明,没有方法体。
- 包含抽象方法的类一定是抽象类,反之,抽象类可以没有抽象方法
- 若子类重写了父类中所有的方法,此类可以实例化
- 若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要使用
abstract
关键字修饰
4.2 抽象类的应用场景及注意点
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
abstract
使用中的注意点;
abstract
不能用来修饰属性、构造器等结构abstract
不能用来修饰私有方法(不可重写)、静态方法(不认为是重写)、final
方法(不可重写)、final
的类(不可继承)
4.3 抽象类的匿名子类
假设存在以下两个类:
abstract class Person {
String name;
int age;
public Person() {
}
public Person(String name, int age) {
}
public abstract void eat();// 抽象方法
}
class Student extends Person {
@Override
public void eat() {
System.out.println("学生吃饭");
}
}
Person
类为抽象类,Student
类为Person
的一个子类,则:
public class PersonTest {
public static void main(String[] args) {
method(new Student());// 非匿名类匿名对象
// 匿名类的对象
Person p = new Person() {
@Override
public void eat() {
}
};
}
public static void method(Student s) {
}
}
上述代码的main
方法中使用了一个匿名类的对象,即直接将Person
类中的抽象方法在创建对象时进行实现,这就是抽象类的匿名子类。
4.4 抽象的应用——模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板;子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一中模板模式。
public class TemplateTest {
public static void main(String[] args) {
Template t = new SubTemplate();
t.spendTime();
}
}
abstract class Template {
public void spendTime() {
long start = System.currentTimeMillis();
code();// 易变的部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start) + "ms");
}
public abstract void code();
}
class SubTemplate extends Template {
@Override
public void code() {
for (int i = 2; i <= 1000; i++) {
boolean isFlag = true;
for (int j = 2; j < Math.sqrt(i); j++) {
if (i % j == 0) {
isFlag = false;
break;
}
}
if (isFlag) {
System.out.println(i);
}
}
}
}
5. 接口
5.1 概述
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有
is a
的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3、手机、数码相机、移动硬盘等都支持USB连接。 - 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是······则必须能······”的思想。继承是一个“是不是”的关系,而接口实现则是“能不能”的关系。
- 接口的本质是契约、标准、规范。就像我们的法律一样,制定好后大家都要遵守
5.2 接口的使用
- 接口使用
interface
定义 - Java中,接口和类是并列的两个结构
如何定义接口:定义接口中的成员
- JDK7及以前,只能定义全局常量和抽象方法
- JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
- 接口中不能定义构造器,意味着接口不可以实例化
- Java开发中,接口通过让类实现(implement)的方式来使用,如果实现类重写了接口中覆盖了接口中所有方法,则此类可以实例化,如果没有重写所有,则此类仍为一个抽象类。
- Java类可以实现多个接口,弥补了Java类单继承性的局限性,格式:
class AA extends BB implements CC, DD, EE{...}
- 接口与接口之间可以继承,且可以多继承。
- 接口具体使用时体现了多态性
- 接口实际上可以看做是一种规范
public class 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();
}
}
interface Flyable {
// 全局常量
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1;// 可以省略“public static final”
// 抽象方法
public abstract void fly();
void stop();// 可以省略“public abstract”
}
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() {
System.out.println("风筝起飞");
}
}
上述代码中,Plane
类实现了Flyable
接口中的所有方法,因此Plane
可以实例化并使用相应的方法,而Kite
类只实现了一个方法,因此Kite
还是一个抽象类,无法实例化。
接口的多实现:
interface Attackable{
void attack();
}
interface Flyable {
// 全局常量
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1;// 可以省略“public static final”
// 抽象方法
public abstract void fly();
void stop();// 可以省略“public abstract”
}
class Bullet implements Flyable,Attackable{
@Override
public void attack() {
System.out.println("子弹攻击");
}
@Override
public void fly() {
System.out.println("子弹飞行");
}
@Override
public void stop() {
System.out.println("子弹停止");
}
}
Bullet
类同时实现了Flyable
、Attackable
两个接口
接口之间的多继承
interface AA {
void method1();
}
interface BB {
void method2();
}
interface CC extends AA, BB {
}
CC
接口同时继承AA
、BB
接口
5.3 接口的匿名实现类对象
与抽象类一样,接口也可以直接实现匿名对象
public class InterfaceTest {
public static void main(String[] args) {
// 匿名实现类
method(new Flyable() {
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
});
}
public static void method(Flyable flly) {
}
}
5.4 接口的应用——代理模式
应用场景
- 安全代理:屏蔽对真实角色的直接访问
- 远程代理:通过代理类处理远程方法调用(RM)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
分类
- 静态代理:静态的定义代理类
- 动态代理:动态生成代理类(JDK自带的代理类,需要了解反射等知识)
public class NetworkTest {
public static void main(String[] args) {
Server server = new Server();
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;
}
@Override
public void browse() {
check();
work.browse();
}
public void check() {
System.out.println("联网前的准备工作");
}
}
5.5 接口使用中的注意点
- 如果父类与实现的接口中有属性重名,则调用父类中的属性时需要使用
super
关键字,调用接口中的变量时使用接口名。变量
,这是因为接口中的变量默认为全局常量 - 接口中定义的变量一定是静态、
final
的 - 如果实现的两个接口中有方法重名,则认为此方法同时是两个接口的实现。
5.6 Java8中接口新特性
可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是看起来违反了接口作为一个抽象类定义的理念
- 静态方法:使用
static
关键字修饰,可以通过接口直接调用静态方法,并执行其他方法体。我们经常在相互一起使用的类中使用静态方法。 - 默认方法:默认方法使用
default
关键字修饰。可以通过实现类对象调用。在已有接口中提供新方法的同时,还保持了与旧版代码的兼容性。如果实现类重写了接口中的默认方法,调用时仍然调用重写后的方法。
- 静态方法:使用
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。(类优先原则)
- 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,会报错(接口冲突)。这就需要我们必须在实现类中重写此方法。
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
CompareA.method1();
s.method2();
s.method3();
}
}
class SubClass implements CompareA {
@Override
public void method2() {
CompareA.super.method2();// 调用接口中的默认方法
}
}
interface CompareA {
// 静态方法
public static void method1() {
System.out.println("CompareA.method1");
}
// 默认方法
public default void method2() {
System.out.println("CompareA.method2");
}
default void method3() {
System.out.println("CompareA.method3");
}
}
6. 内部类
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部结构最好使用内部类。
- 在Java中,允许一个类定义一于另一个类的内部,前者称为内部类,后者称为外部类
Inner class
一般定义在它的类或语句块之内,在外部引用时必须给出完整的名称(Inner class
的名字不能与包含它的外部类类名相同)分类:
成员内部类(static成员内部类和非static成员内部类)
- 类内可以定义属性、方法、构造器等
- 可以被
final
修饰,表示此类不能被继承(不使用final
修饰即可以被继承)。 - 可以被
abstract
修饰 - 可以调用外部类的成员:
外部类.this.成员
- 可以被
static
修饰可以被四种不同权限修饰
- 局部内部类(不谈修饰符)、匿名内部类,可位于方法、代码块、构造器内
class Person {
public Person() {
class CC {
}
}
public void method() {
// 局部内部类
class AA {
}
}
{
// 局部内部类
class BB {
}
}
// 成员内部类
class DD{
}
// 静态成员内部类
static class EE{
}
}
6.1 如何实例化成员内部类对象
Person.EE ee = new Person.EE();// 实例化静态内部类
Person p = new Person();
Person.DD dd = p.new DD();// 实例化非静态内部类
静态内部类可以使用外部类.内部类 变量名 = new 外部类.内部类()
的方式进行实例化,而非静态内部类则必须先实例化外部类,用外部类的对象调用内部类的构造函数。
6.2 开发中局部内部类的使用
public class InnerClass1 {
// 开发中很少见
public void method() {
// 局部内部类
class AA {
}
}
// 返回一个实现了Compareable接口的类的对象(方式一)
public Comparable getComparable() {
class MyComparable implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();
}
// 方式二
public Comparable getComparable2() {
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}