类变量
P373~P423
类变量也叫静态变量
static变量是同一个类的所有对象共享
static变量在类加载的时候就生成
如何定义类变量
访问修饰符 static 数据类型 变量名;
如何访问类变量
- 类名.类变量名
- 对象名.类变量名
静态变量的访问修饰符的访问权限和范围和普通属性一样
类变量使用细节
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量
类方法
访问修饰符 static 数据返回类型 方法名(){}
类方法调用
- 类名.类方法名
- 对象名.类方法名
类方法使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
如果我们不希望创建实例,也可以调用某个方法,这时,把方法做成静态方法非常合适
比如:工具类中的方法
Math类、Arrays类、Collections集合类
类方法注意事项
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中无this的参数,普通方法中隐含着this的参数
- 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用
- 类方法中不允许使用和对象有关的关键字,比如this和super
- 类方法中只能访问静态变量或静态方法
- 普通成员方法既可以访问非静态成员,也可以访问静态成员
main方法
public static void main(String[] args){}
- public:java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- static:java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接受Sting类型的数组参数,该数组中保存执行java命令时传递给所运行类的参数
注意
main方法中,可以直接调用main方法所在类的静态方法或静态属性
但是不能直接访问该类中的非静态成员,必须创建该类的一个实力对象后,才能通过这个对象去访问类中的非静态成员
代码块
代码块又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用
基本语法
[修饰符]{
代码
};
注意
- 修饰符可选,但写的话只能是static
- 代码块分为两类,static修饰的叫静态代码块,没有static修饰的叫普通代码块
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断)
- 分号可以写也可以省略
理解
- 相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
- 不管调用哪个构造器创建对象,都会先调用代码块的内容
- 代码块调用的顺序优先于构造器
代码块使用细节,com.codeBlock.CodeBlockDetail.java
- static代码块也叫静态代码块,作用是对类进行初始化,且随着类的加载而执行,并且只会执行一次。如果是普通代码块,创建一个对象,就执行一次。
- 类什么时候被加载
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载,且父类先加载,子类后加载
- 使用类的静态成员时(静态属性,静态方法)
- 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次,相当于构造器的补充。如果只是使用类的静态成员,普通代码块并不会执行。
- 创建一个对象时,在一个类调用顺序是:
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
- 调用构造方法
- 构造器的最前面其实隐含了super()和调用普通代码块。静态的代码块和属性初始化在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的。CodeBlockDetail03.java
- 创建一个子类时,静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块,静态属性初始化
- 子类的静态代码块,静态属性初始化
- 父类的普通代码块,普通属性初始化
- 父类的构造函数
- 子类的普通代码块,普通初始化
- 子类的构造函数
- 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员
package com.codeBlock;
public class CodeBlockDetail {
public static void main(String[] args) {
//1. static代码块也叫静态代码块,作用是对类进行初始化,且随着类的加载而执行,并且只会执行一次。
//如果是普通代码块,每创建一个对象,就执行。
//2. 类什么时候被加载
//1. 创建对象实例时(new)
//2. 创建子类对象实例,父类也会被加载
// AA aa = new AA(); // bb的静态代码块被执行
// aa的静态代码块被执行
// bb的普通代码块
//3. 使用类的静态成员时(静态属性,静态方法)
// System.out.println(Cat.name); //cat的静态代码块被执行
//mao
//3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。
//如果只是使用类的静态成员,普通代码块并不会执行。
// BB.show(); //bb的静态代码块被执行
//调用静态属性,普通代码块不调用
// BB bb1 = new BB(); //bb的静态代码块被执行
//bb的普通代码块
}
}
class BB {
static {
System.out.println("bb的静态代码块被执行");
}
{
System.out.println("bb的普通代码块");
}
public static void show() {
System.out.println("调用静态属性,普通代码块不调用");
}
}
class AA extends BB {
static {
System.out.println("aa的静态代码块被执行");
}
}
class Cat{
public static String name="mao";
static {
System.out.println("cat的静态代码块被执行");
}
}
设计模式
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
单例模式
- 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式:饿汉式、懒汉式
单例模式应用实例
【饿汉式】,com.single_.SingleTon01.java
只要类加载,这个对象就创建,可能只是用一下类变量,即使没有使用到,这个对象也给一起创建了,会浪费资源
步骤:
-
构造器私有化:防止直接new
-
类的内部创建对象
-
向外暴露一个静态的公共方法。getInstance
(为什么要static?因为如果要不创建实例直接调用类方法,那他是static的,且方法内的属性也是static的)
(如果调用多次getInstance用不同变量名接收,其实是指向同一个对象,因为static的属性只会初始化一次)
-
代码实现
public class SingleTon01 {
public static void main(String[] args) {
// GirlFriend xiaohong = new GirlFriend("xiaohong");
// GirlFriend xiaobai = new GirlFriend("xiaobai");
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
}
}
class GirlFriend {
private String name;
//如何保证只能有一个girlfriend
//步骤:
//1、构造器私有化
//2、类的内部创建
private static GirlFriend gf = new GirlFriend("xiaohong");
private GirlFriend(String name) {
this.name = name;
}
//3、提供一个公共静态方法
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" + "name='" + name + '\'' + '}';
}
}
【懒汉式】,com.single_.SingleTon02.java
只有用户调用getInstance方法时,才会创建对象,且如果调用过这个方法了,再次调用时,返回的是上次创建的对象
public class SingleTon02 {
public static void main(String[] args) {
System.out.println(Cat.getInstance());
}
}
//希望程序运行过程中只能创建一个Cat
class Cat{
private String name;
private static Cat cat;
//1、构造器私有化
//2、定义一个static属性对象
private Cat(String name) {
this.name = name;
}
//3、提供公共static方法,返回cat对象
public static Cat getInstance(){
if(cat==null){
cat= new Cat("xiaomao");
}
return cat;
}
饿汉式和懒汉式的区别
- 两者最主要的区别在于创建对象的时机不同,饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(见线程学习)
- 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
- 在javaSE标准类中,java.lang.Runtime就是经典的单例模式
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
Final关键字
final可以修饰类、属性、方法和局部变量
使用情况:com.final_.Final01.java
- 当不希望类被继承时,可以使用final修饰
- 当不希望父类的某个方法被子类覆盖/重写时【访问修饰符 final 返回类型 方法名】
- 当不希望类的某个属性的值被修改【public final double TAX_RATE=0.08】
- 当不希望某个局部变量被修改【final double TAX_RATE=0.08】
package com.final_;
public class Final01 {
public static void main(String[] args) {
A a = new A();
a.TAX_RATE=0.09;
}
}
//如果要求A不能被其他类继承,使用final修饰A
//final class A{
class A{
// 不希望类的某个属性的值被修改
// public final double TAX_RATE=0.08;
public double TAX_RATE=0.08;
// 不希望方法被子类覆盖
// public final void hi(){}
public void hi(){
// 不希望某个局部变量被修改
// final int money=100;
int money=100;
money =101;
}
}
class B extends A{
@Override
public void hi() {
super.hi();
}
}
final细节
- final修饰的属性又叫常量,用XX_XX_XX来命名
- final修饰的属性在定义时必须赋初值,并且不能再修改,可以在如下位置赋初值:
- 定义时
- 在构造器中
- 在代码块中
class AA{
public final double TAX_RATE=0.08;//定义时
public final double TAX_RATE2;
public final double TAX_RATE3;
public AA(){//构造器中赋值
TAX_RATE2=1.1;
}
{//代码块赋值
TAX_RATE3=8.8;
}
}
- 如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 静态代码块
class BB{
public static final double TAX_RATE=0.08;//定义时
public static final double TAX_RATE2;
static {//代码块赋值
TAX_RATE3=8.8;
}
}
- final类不能继承,但是可以实例化对象
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
- 一般来说,如果一个类是final类了,就不用再把方法写成final的
- final不能修饰构造器
- final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理com.final_.FinalStatic.java
package com.final_;
public class FinalStatic {
public static void main(String[] args) {
System.out.println(AA.name);
}
}
class AA {
public static final String name = "aaa";
static {
System.out.println("静态代码块");
}
}
- 包装类(Integer,Double,Float,Boolean都是final),String也是final类
抽象类
com.abstract_.Abstract01.java
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
package com.abstract_;
public class Abstract01 {
public static void main(String[] args) {
}
}
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
//eat实现了,但没什么意义
//出现了父类方法不确定性的问题
//考虑将该方法设计为抽象方法
//所谓抽象方法就是没有实现的方法
//所谓没有实现就是没有方法体{}
//当一个类中存在抽象方法时,需要将该类声明为abstract类
//一般来说,抽象类会被继承,由其子类来实现抽象方法
public abstract void eat();
}
介绍
-
用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{}
-
用abstract关键字来修饰一方法时,这个方法就是抽象方法,没有方法体{}
访问修饰符 abstract 返回类型 方法名(参数列表);
-
抽象类的价值更多作用是在于设计,是设计者设计好之后,让子类继承并实现抽象类
抽象类细节
- 抽象类不能被实例化
- 抽象类不一定包含abstract方法,也就是说抽象类可以没有abstract方法
- 但是如果类包含了abstract方法,则这个类必须声明为abstract
- abstract只能修饰类和方法,不能修饰属性和其他
- 抽象类可以有任意成员,因为抽象类还是类
- 抽象方法不能有方法体{}
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非他自己也是抽象类
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的
练习,com.abstract_.AbstractExercise01.java
编写一个Employee类,声明为抽象类,包含三个属性:name、id、salary,提供必要的构造器和抽象方法work()。对于Manager类来说,他既是员工,还有奖金(bonus)的属性,请使用继承思想,设计CommonEmployee和Manager类,要求类中提供必要的方法进行属性访问,实现work(),“经理、普通员工 姓名 工作中。。。”
package com.abstract_;
public class AbstractExercise01 {
public static void main(String[] args) {
CommonEmployee xiaoming = new CommonEmployee("小明", 101, 1000);
Manager daming = new Manager("大明", 99, 1100, 2000);
xiaoming.work();
daming.work();
}
}
abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public double getSalary() {return salary;}
public void setSalary(double salary) {this.salary = salary;}
public abstract void work();
}
class Manager extends Employee {
private double bonus;
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
public double getBonus() {return bonus;}
public void setBonus(double bonus) {this.bonus = bonus;}
@Override
public void work() {
System.out.println("经理 " + getName() + "工作中..");
}
}
class CommonEmployee extends Employee {
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工" + getName() + "工作中..");
}
}
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来
interface 接口名{
//属性
//方法(1抽象方法2默认方法3静态方法)
}
class 类名 implements 接口名{
自己属性;
自己方法;
必须实现接口的抽象方法
}
在JDK7.0前,接口里的所有方法都没有方法体
在JDK8.0后,接口类可以有静态方法需要static修饰,默认方法需要default修饰,也就是说接口中可以有方法的具体实现
快速入门,com.interface_.Interface01.java
package com.interface_;
public class Interface01 {
public static void main(String[] args) {
Computer computer = new Computer();
Camera camera = new Camera();
Phone phone = new Phone();
computer.work(camera);
computer.work(phone);
}
}
interface UsbInterface {
public void start();
public void stop();
}
class Computer {
public void work(UsbInterface usbInterface) {
usbInterface.start();
usbInterface.stop();
}
}
class Camera implements UsbInterface {
@Override
public void start() {
System.out.println("我是相机,开始工作");
}
@Override
public void stop() {
System.out.println("我是相机,结束工作");
}
}
class Phone implements UsbInterface {
@Override
public void start() {
System.out.println("我是手机,开始工作");
}
@Override
public void stop() {
System.out.println("我是手机,结束工作");
}
}
接口细节
-
接口不能被实例化
-
接口中所有的方法都是public方法,接口中抽象方法可以不用abstract修饰
-
一个普通类implements接口,就必须实现接口的所有方法
-
抽象类implements接口,可以不用实现接口的方法
-
一个类同时可以implements多个接口
-
接口中的属性只能是final的,而且是public static final。比如 int a=1;实际上是public static final int a=1;
-
接口中属性的访问形式:接口名.属性名
-
一个接口不能继承其他的类,但是可以继承多个别的接口
interface extends B,C{}
-
接口的修饰符只能是public和默认,和类的修饰符一样
实现接口vs继承类
接口和继承解决的问题不同
- 继承的价值在于:解决代码的复用性和可维护性
- 接口的价值在于:设计,设计各种规范,让其他类去实现这些方法
接口在一定程度上实现代码解耦
抽象类和接口的使用时机
在设计类的时候,首先考虑用接口抽象出类的特性,当你发现某些方法可以复用的时候,可以使用抽象类来复用代码。简单说,接口用于抽象事物的特性,抽象类用于代码复用。
当然,不是所有类的设计都要从接口到抽象类,再到类。程序设计本就没有绝对的范式可以遵循。根据自己的需求设计。
接口的多态特性
- 多态参数,InterfacePolyParameter.java
- 多态数组,InterfacePloyArr.java
- 接口存在多态传递现象,InterfacePolyPass.java
小结
类的五大成员:属性、方法、构造器、代码块、内部类
package 包名;
class 类名 extends 父类 implements 接口名{
成员变量//属性
构造方法//构造器
成员方法//方法
代码块
}
内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类,嵌套其他类的类称为外部类。
内部类的最大特点就是可以直接访问私有属性,并且可以体现类于类之间的包含关系
基本语法
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部其他类
}
内部类的分类
定义在外部类局部位置上:比如方法内
- 局部内部类(有类名)
- 匿名内部类(没有类名)
定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
定义在外部类局部位置上:
局部内部类,com.innerclass.LocalInnerClass.java
- 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
- 不能添加访问修饰符,因为他的地位就是一个局部变量。局部变量不能使用修饰符,但是可以用final修饰
- 作用域:仅在定义他的方法或代码块中,相当于一个类型为类的局部变量
- 局部内部类直接访问外部类成员,包括私有的
- 外部类需要创建对象再访问内部类成员
- 外部其他类不能访问局部内部类
- 如果外部类和局部内部类重名时,默认遵循就近原则,如果吸纳各方那个问外部类的成员,可以使用外部类名.this.成员访问
public class LocalInnerClass {
public static void main(String[] args) {
}
}
class Outer {
private int n1 = 100;
private void m2() {}
private void m1() {
// 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
// 不能添加访问修饰符,因为他的地位就是一个局部变量。
// 局部变量不能使用修饰符,但是可以用final修饰
class Inner { // 局部内部类
// 局部内部类可以直接访问外部类成员,包括私有的
public void f1() {
System.out.println("n1=" + n1);
}
}
}
}
- 局部内部类定义在方法/代码块中
- 作用域在方法体/代码块中
- 本质是个类
匿名内部类,com.anonymousInner.AnonymousInnerClass.java
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名,同时还是一个对象
匿名内部类相当于实现接口或继承父类
匿名内部类的基本语法
new 类或接口(参数列表){
匿名类类体
};
- 匿名内部类既是一个类的定义,同时他也是一个对象,因此从语法上看,他既有类的特征,也有创建对象的特征,因此可以调用匿名内部类方法
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为他的地位就是一个局部变量
- 作用域:仅在定义他的方法或代码块中
- 匿名内部类直接访问外部类的成员
- 外部其他类不能访问匿名内部类
package com.anonymousInner;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 {//外部类
private int n1 = 10;//属性
public void method() {//方法
//基于接口的匿名内部类
//1、需求:想使用接口IA,并创建对象
//2、传统方式:写一个类,实现该接口,然后创建对象
//3、需求是Tiger类只使用一次,后面再不使用
// IA tiger = new Tiger();
// tiger.cry();
//4、可以用匿名内部类来简化开发
//演示基于接口的匿名内部类
//tiger的编译类型:IA
//tiger的运行类型:com.anonymousInner.Outer04$1
//底层:class Outer04$1 implements IA{}
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫");
}
};
tiger.cry();
System.out.println("tiger的运行类型"+tiger.getClass());
//演示基于类的匿名内部类
//jack的编译类型:
//jack的运行类型:com.anonymousInner.Outer04$2
//底层:class Outer04$2 extends Father{}
//和""Father jack = new Father("jack");"" 有区别,区别在于"{};",
//实际是匿名内部类继承了Father类,应该是叫向上转型
Father jack = new Father("jack") {};
System.out.println("jack的运行类型"+jack.getClass());
}
}
interface IA {
public void cry();
}
// 传统方法:写一个类,实现该接口,然后创建对象
class Tiger implements IA {
@Override
public void cry() {
System.out.println("老虎叫");
}
}
class Father {
public Father(String name) {super();}
public void test() {}
}
内部类练习,com.anonymousInner.InnerClassExercise02.java
- 有一个铃声接口Bell,里面有个ring方法
- 有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型
- 测试手机类的闹钟功能,通过匿名内部类作为参数,打印:懒猪起床了
- 再传入另一个匿名内部类,打印:小伙伴上课了
//1. 有一个铃声接口Bell,里面有个ring方法
//2. 有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型
//3. 测试手机类的闹钟功能,通过匿名内部类作为参数,打印:懒猪起床了
//4. 再传入另一个匿名内部类,打印:小伙伴上课了
public class InnerClassExercise02 {
public static void main(String[] args) {
new Cellphone().alarmclock(new Bell(){
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
new Cellphone().alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell{
public void ring();
}
class Cellphone{
public void alarmclock(Bell bell){bell.ring();}
}
定义在外部类的成员位置上:
成员内部类
- 可以直接访问外部类的所有成员,包括私有的
- 可以添加任意访问修饰符 public、protected、默认、private,地位相当于成员
- 作用域:和外部类的其他成员一样,为整个类体
- 成员内部类直接访问外部类
- 外部类访问内部类:创建对象在访问
- 外部其他类访问成员内部类
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)访问
静态内部类
静态内部类是定义在外部类的成员位置,并且有static修饰
- 可以直接访问外部类的所有静态成员,包括私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符
- 作用域:同其他成员,为整个类体
- 静态内部类直接访问外部类
- 外部类创建对象访问静态内部类