一、类变量和类方法
1. 类变量的概述
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问
它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
2. 类变量的定义
定义语法:
访问修饰符 static 数据类型 变量名;//推荐
或
static 访问修饰符 数据类型 变量名;
3. 类变量的访问
类名.类变量名; //推荐 ,静态变量访问修饰符的访问权限和访问范围和普通属性一样
或
对象名.类变量名;
4. 类变量使用注意事项
① 什么时候需要使用:
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)
比如:定义学生类,统计所有学生共交多少钱。
Student (name,static fee)
② 类变量与示例变量 (普通属性) 区别
类变量是该类的所有对象共享的,而示例对象是每个对象独享的
③ 加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
④ 类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但Java设计者推荐我们
使用类名.类变量名方式访问。【前提是 满足访问修饰符的访问权限和访问范围】
⑤ 实例变量不能通过 类名.类变量名 方式访问
⑥ 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,
就可以使用类变量了
⑦ 类变量的生命周期是随类的加载开始,随着类消亡而销毁
5. 类方法基本介绍
类方法也叫静态方法
形式如下:
访问修饰符 static 数据返回类型 方法名(){} //推荐
或
static 访问修饰符 数据返回类型 方法名(){}
6. 类方法的调用
类名.类方法名 //前提是满足访问修饰符的访问权限和访问范围
或
对象名.类方法名
7. 类方法使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
例如:工具类中的方法 utils
Math类、Arrays类、Collections集合类看下源码;
小结:
在程序员实际开发,往往会将一些通用的方法设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等
8. 类方法使用注意事项
① 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无 this 的参数
普通方法中隐含着 this 的参数
② 类方法可以通过类名调用,也可以通过对象名调用
③ 普通方法和对象有关,需要通过对象名调用
例如:对象名.方法名(参数),不能通过类名调用(需要先创建对象再调用)
④ 类方法中不允许使用和对象有关的关键字,比如 this 和 super
普通方法(成员方法)可以
⑤ 类方法(静态方法)中,只能访问静态变量 或静态方法
⑥ 普通成员方法,既可以访问非静态成员,也可以访问静态成员
小结:
静态方法,只能访问静态的成员,
非静态的方法,可以访问静态成员和非静态成员
(必须遵守访问权限)
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
// this.total = total;//错误,因为在 static 方法中,不可以使用 this 关键字
Person.total = total;
}
public Person() {//构造器
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson(3);
new Person(); //最后 total 的值就是 4
}
}
二、理解 main 方法语法
1. 解释 main 法的形式
public static void main(String[] args){}
① main 方法时虚拟机调用
② Java 虚拟机需要调用类的 main() 方法,所有该方法的访问权限必须是 public
③ Java 虚拟机在执行 main()方法时不必创建对象,所有该方法必须是 static
④ 该方法接收 String 类型的数组参数,该数组中保存执行Java 命令时传递给所
运行类的参数,接收参数
⑤ Java 执行的程序 参数1 参数2 参数3
2. 注意事项
① 在 main() 方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性
② 但是,不能直接访问该类中的非静态成员,必须访问该类的一个实例对象后,
才能通过这个对象去访问类中的非静态成员(必须创建对象后才能用)
三、代码块
1. 代码块的概念
代码块又称初始化块,属于类中的成员(即是类中的一部分),类似于方法,将逻辑语句
封装在方法体中,通过{}包围起来
但是和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类
显示调用,而是加载类时,或创建对象时隐式调用
2. 基本语法
[修饰符]{
代码
};
注意:
① 修饰符 可选,要写的话,也只能写 static
② 代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,
叫普通代码块/非静态代码块
③ 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
④ ;可以写上,也可以省略
3. 代码块的好处
① 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
② 场景:如果多个构造器中都有重复语句,可以抽取到初始化块中,提高代码的重用性
③ 代码块的调用优先于构造器
4. 代码块使用注意事项
(1) static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,
并且只会执行一次,如果是普通代码块,每创建一个对象,就执行
(2) 类什么时候被加载 (重点)
① 创建对象时(new)
② 创建子类对象实例,父类也会被加载 (继承的本质先加载父类再加载子类)
③ 使用类的静态成员时(静态属性,静态方法)
(3) 普通的代码块,在创建对象实例时,会被隐式的调用
被创建一次,就会调用一次
如果只是使用类的静态成员时,普通代码块并不会执行
小结:
① static 代码块是类加载时,执行,只会执行一次
② 普通代码块是在创建对象时调用的,创建一次,调用一次
③ 类加载的3种情况,需要记住
(4) 创建一个对象时,在一个类调用顺序是:(重点)
① 调用静态代码块和静态属性初始化
(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和
多个静态变量初始化,则按定义顺序调用)
② 调用普通代码块和普通属性的初始化
(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和
多个普通属性初始化,则按定义顺序调用)
③ 调用构造方法
(5) 构造器 的最前面其实隐含了 super() 和调用普通代码块,静态相关的代码块,属性初始化,
在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
class A{
public A(){ //构造器
// 这里有隐藏的执行要求
// ① super();
// ② 调用普通代码块的
System.out.println("ok");
}
}
(6) 创建一个子类对象时(继承关系),静态代码块,静态属性初始化,普通代码块,普通属性
初始化,构造方法的顺序如下 :
① 父类的静态代码块和静态属性 (优先级一样,按定义顺序执行)
② 子类的静态代码块和静态属性 (优先级一样,按定义顺序执行)
③ 父类的普通代码块和普通属性 (优先级一样,按定义顺序执行)
④ 父类的构造方法
⑤ 子类的普通代码块和普通属性 (优先级一样,按定义顺序执行)
⑥ 子类的构造方法
(7) 静态代码块只能直接调用静态成员(静态属性和静态方法),
普通代码块可以调用任意成员
class Person {
public static int total;//静态变量
static {//静态代码块
total = 100;
System.out.println("in static block!");//(1),静态代码只在类加载的时候执行一次
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = "+ Person.total); //100
System.out.println("total = "+ Person.total); //100
}
}
四、单例设计模式
1. 设计模式的概念
① 静态方法和属性的经典使用
② 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决 问题的思考方式。
设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考 和摸索
2. 什么是单例模式
单例(单个的实例):
① 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存 在一个对象实例,并且该类只提供一个取得其对象实例的方法
② 单例模式有两种方式:懒汉式和饿汉式
3. 单例模式的应用
① 构造器私有化 =》防止直接 new
② 类的内部创建对象(该对象是static)
③ 向外暴露一个静态的公共方法。getlnstance
package com.hhh.single_;
public class SingleTon01 {
public static void main(String[] args) {
// GirlFriend xh = new GirlFriend("小红");
// GirlFriend xb = new GirlFriend("小白");
//通过方法可以获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
//System.out.println(GirlFriend.n1); //...
}
}
//有一个类, GirlFriend
//只能有一个女朋友
class GirlFriend {
private String name;
//public static int n1 = 100;
//为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static
//對象,通常是重量級的對象, 餓漢式可能造成創建了對象,但是沒有使用.
private static GirlFriend gf = new GirlFriend("小红红");
//如何保障我们只能创建一个 GirlFriend 对象
//步骤[单例模式-饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是 static)
//3. 提供一个公共的 static 方法,返回 gf 对象
private GirlFriend(String name) {
System.out.println("構造器被調用.");
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
package com.hhh.single_;
/**
* 演示懶漢式的單例模式
*/
public class SingleTon02 {
public static void main(String[] args) {
//new Cat("大黃");
//System.out.println(Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次調用 getInstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
}
}
//希望在程序運行過程中,只能創建一個 Cat 對象
//使用單例模式
class Cat {
private String name;
public static int n1 = 999;
private static Cat cat ;
//默認是 null
//步驟
//1.仍然構造器私有化
//2.定義一個 static 靜態屬性對象
//3.提供一個 public 的 static 方法,可以返回一個 Cat 對象
//4.懶漢式,只有當用戶使用 getInstance 時,才返回 cat 對象,
// 後面再次調用時,會返回上次創建的 cat 對象
// 從而保證了單例
private Cat(String name) {
System.out.println("構造器調用...");
this.name = name;
}
public static Cat getInstance() {
if(cat == null) {//如果還沒有創建 cat 對象
cat = new Cat("小可愛");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
4. 饿汉式 和 懒汉式的区别
① 二者最主要的区别在于创建对象的时机不同:
饿汉式是在类加载就创建了对象实例
而懒汉式是在使用时才创建
② 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
③ 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式
创建的对象就浪费了
懒汉式是使用时才创建,就不存在这个问题
④ 在我们 JavaSE 标准类中,java.lang.Runtime 就是经典的单例模式
五、final 关键字
final 是最后的、最终的,可以修饰类、属性、方法和局部变量。
在以下情况下会用到:
① 当不希望类被继承时,可以用 final 修饰
② 当不希望父类的某个方法被子类 覆盖/重写(overrider) 时,可以用 final 关键字修饰
访问修饰符 final 返回类型 方法名
③ 当不希望类的某个属性的值被修改,可以用 final 修饰
④ 当不希望某个局部变量被修改,可以使用 final 修改
(2) final 使用注意事项
1) final 修饰的属性又叫常量,一般用 XX_XX_XX 来命名
2) final 修饰的属性在定义时,必须赋初值,并且以后不能再修改
赋值可以在如下位置之一:
① 定义时:如 public final double TAX_RATE=0.08;
② 在构造器中
③ 在代码块中
3) 如果 final 修饰的属性是静态的,则初始化的位置只能是:
① 定义时
② 在静态代码块,不能在构造器中赋值
4) final 不能继承,但是可以实例化对象
5) 如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法
6) final 不能修饰构造方法
7) final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
class Demo{
public static final int i = 16;
static{
System.out.println("hahaha");
}
}
8) 包装类(Integer,Double,Float,Boolean等都是 final),String 也是 final 类
六、抽象类 (abstract)
当父类的某些方法需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,
(用 abstract 修饰),那么这个类就是抽象类。
abstract class Animal{
String name;
int age;
abstract public void cry();
}
2. 抽象类的介绍
① 用 abstract 关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{}
② 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
③ 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
④ 抽象类,在框架和设计模式使用较多
3. 注意事项
① 抽象类不能被实例化
② 抽象类不一定要包含 abstract 方法,也就说,抽象类可以没有 abstract 方法
③ 一旦类包含了 abstract 方法,则这个类必须声明为 abstract
④ abstract 只能修饰类和方法,不能修饰属性和其他的
⑤ 抽象类可以有任意成员(抽象类的本质还是类)
比如:非抽象方法、构造器、静态属性等
⑥ 抽象方法不能有主体
⑦ 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,
除非它自己也声明为 abstract 类
⑧ 抽象方法不能使用 private、final 和 static 来修饰,因为这些字都是和重写相
违背的。
4. 抽象类最佳实践--模板设计模式
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在
抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
(2) 模板设计模式能解决的问题
① 当功能内部一部分实现是确定,一部分实现是不确定的,这时可以不确定的部分
暴露出去,让子类去实现
② 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给
其子类实现,就是一种模板模式
七、接口 (interface)
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,
在根据具体情况把这些方法写出来。
语法:
interface 接口名{
//属性
//方法 ① 抽象方法
② 默认实现方法(需要使用default关键字修饰) JDK 8以后才能
③ 静态方法 JDK 8以后才能
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法
}
小结:接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法
都没有方法体。//JDK7.0
接口体现了程序设计的多态和高内聚低耦合的设计思想。 //注:JDK8.0 后接口类可以有
静态方法,默认方法,也就是说接口中可以有方法的具体实现
2. 注意事项
① 接口不能被实例化
② 接口中所有的方法是 public 方法,接口中抽象方法,可以不用 abstract 修饰
void aaa();
实际上是 abstract void aa();
③ 一个普通类实现接口,就必须将该接口的所有方法都实现
④ 抽象类实现接口,可以不用实现接口的方法
⑤ 一个类同时可以实现多个接口
⑥ 接口中的属性,只能是 final 的,而且是 public static final 修饰符。
比如:int a = 1;
实际上是 public static final int a =1;//必须初始化
⑦ 接口中属性的访问形式:接口名.属性名
⑧ 接口不能继承其他的类,但是可以继承多个别的接口
interface A extends B,C{}
⑨ 接口的修饰符,只能是 public 和默认,这点和类的修饰符是一样的。
3. 接口和继承的区别
(1) 解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法
(2) 接口比继承更加灵活
继承是满足 is - a 的关系,而接口只需满足 like - a 的关系
(3) 接口在一定程度上实现代码解耦,(即:接口规范性+动态绑定机制)
4. 接口的多态特性
① 多态参数
② 多态数组
③ 接口存在多态传递现象
八、内部类(重点)InnerClass
如果定义类在局部位置(方法中/代码块):
① 局部内部类
② 匿名内部类
定义在成员位置:
① 成员内部类
② 静态内部类
1. 基本介绍
一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类 (inner class),
嵌套其他的类的类称为外部类 (outer class)。是我们类的第五大成员
五大成员分别为:属性、方法、构造器、代码块、内部类。
内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
2. 基本语法
class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}
3. 内部类的分类
定义在外部类的局部位置 (方法中/代码块):
① 局部内部类 (有类名)
② 匿名内部类 (没有类名)
定义在外部类的成员位置:
① 成员内部类 (没用static修饰)
② 静态内部类 (使用static修饰)
4. 局部内部类的使用 (LocalInnterClass)
局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
package com.hhh.innerclass;
public class LocalInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02 的 hashcode=" + outer02);
}
}
class Outer02 {
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
}//私有方法
public void m1() {//方法
final class Inner02 {
private int n1 = 800;
public void f1() {
//Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1,
//Outer02.this 就是哪个对象
System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashcode=" + Outer02.this);
m2();
}
}
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
① 可以直接访问外部类的所有成员,包含私有的
② 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用
修饰符的,但是可以使用 final 修饰,因为局部变量也可以使用 final。
③ 作用域:仅仅在定义它的方法或代码块中
④ 局部内部类---访问--->外部类的成员 [访问方式:直接访问]
⑤ 外部类---访问--->局部内部类的成员
访问方式 :创建对象,再访问(注意:必须在作用域内)
记住:
(1) 局部内部类定义在方法中/代码块
(2) 作用域在方法体或代码块中
(3) 本质仍然是一个类
⑥ 外部其他类---不能访问--->局部内部类 (因为局部内部类地位是一个局部变量)
⑦ 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类
的成员,则可以使用 (外部类名.this.成员) 去访问
System.out.println("外部类的n2=" + 外部类名.this.n2);
5. 匿名内部类的使用(重点 AnonymousInnterClass)
① 本质是类
② 内部类
③ 该类没有名字
④ 同时还是一个对象
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
(1) 基本语法:
new 类或接口(参数列表){
类体
};
package com.hhh.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 { //外部类
private int n1 = 10;//属性
public void method() {//方法
//基于接口的匿名内部类
// 要求Tiger/Dog 类只是使用一次,后面再不使用
// tiger 的编译类型 IA
// tiger 的运行类型 , 就是匿名内部类 Outer04$1
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
// jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,
// 并且把地址返回给 tiger
// 匿名内部类使用一次,就不能再使用,但是tiger还可以继续调用
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
};
System.out.println("tiger 的运行类型=" + tiger.getClass());
tiger.cry();
tiger.cry();
tiger.cry();
// IA tiger = new Tiger();
// tiger.cry();
}
(2) 匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,
它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法
(3) 可以直接访问外部类的所有成员,包含私有的
(4) 不能添加访问修饰符,因为它的地位就是一个局部变量
(5) 作用域:仅仅在定义它的方法或代码块中
(6) 匿名内部类---访问--->外部类成员 [访问方式:直接访问]
(7) 外部其他类---不能访问--->匿名内部类(因为它的地位就是一个局部变量)
(8) 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近
原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员) 去访问
匿名内部类最佳实践:当作实参直接传递,简洁高效
package com.hhh.innerclass;
import com.hhh.abstract_.AA;
public class InnerClassExercise01 {
public static void main(String[] args) {
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
//传统方法
f1(new Picture());
}
//静态方法,形参是接口类型
public static void f1(IL il) {
il.show();
}
}
//接口
interface IL {
void show();
}
//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画 XX...");
}
}
6. 成员内部类的使用 (MemberInnterClass)
成员内部类是定义在外部类的成员位置,并且没有 static 修饰
① 可以直接访问外部类的所有成员,包含私有的
class Outer01{ //外部类
private int n1 = 10;
public String name = "张三";
class Innter01{
public void say(){
System.out.println("Outer01 的 n1 =" + n1 + "Outer01 的 name = " + name);
}
}
}
② 可以添加任意访问修饰符(public、protected、默认、private),
因为他的地位就是一个成员。
③ 作用域:和外部类的其他成员一样,为整个类体
比如:在外部类的成员方法中创建成员内部类对象,再调用方法
④ 成员内部类---访问--->外部类成员(如:属性) [访问方式:直接访问]
⑤ 外部类---访问--->成员内部类 [访问方式:创建对象,再访问]
⑥ 外部其他类---访问--->成员内部类
Outer.Inner inner = Outer.new Inner();
// 相当于把new Inner当成是Outer的成员
//第二种方式,可以在外部类中,编写一个可以返回Inner对象的方法
Outer.Inner innerInstance = Outer.getInnerInstance();
innerInstance.say();
⑦ 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,
如果想访问外部类的成员,则可以使用 (外部类名.this.成员) 去访问
7. 静态内部类的使用 (StaticInnerClass)
静态内部类是定义在外部类的成员位置,并且有 static 修饰
① 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问静态成员
② 可以添加任意访问修饰符(public、protected、默认、private),因为他的地位就是
一个成员
③ 作用域:同其他的成员,为整个整体
class Outer{
private int n1 = 10;
private static String name = "张三";
static class Inner{
public void say(){
System.out.println(name);
//不能直接访问外部类的非静态成员
System.out.println(n1);
}
}
public void show(){
//外部类使用内部类
new Inner().say();
}
}
④ 静态内部类---访问--->外部类(如:静态属性) [访问方式:直接访问所有静态成员]
⑤ 外部类---访问--->静态内部类 [访问方式:先创建对象,再访问]
⑥ 外部其他类---访问--->静态内部类
⑦ 如果外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,
如果想访问外部类的成员,则可以使用 (外部类名.this.成员) 去访问