Java基础 | 面向对象(下)

Java基础 | 面向对象(下)

static 关键字

static 修饰的结构在内存中只有一份,不管类的实例对象有多少个

  • static 可以修饰属性、方法、代码块、内部类,不可修饰构造器
  • static 修饰的属性,即静态变量或类变量,是各个对象实例共享的属性,随着类的加载而加载,可以直接使用类名去调用,类只会加载一次,所以静态变量在内存中只会存在一份,存在方法区的静态域中,例如 Math.PI,使用任意对象对静态属性的修改,都会影响其他对象对此属性的使用
  • static 修饰的方法,静态方法,随着类的加载而加载,可以使用类名去调用,所以,静态方法中不可以出现 this、super 关键字,同时静态方法中只可以调用静态的方法、属性

单例设计模式 Singleton

设计模式

设计模式大量实践中总结和理论化之后优选的代码结构、编程风格、解决问题的思考方式

单例设计模式

整个系统中,保证某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

  • 饿汉式:线程安全,即使没有获取该对象实例,也会在内存中加载
  • 懒汉式:延迟对象的创建,需要获取该实例的时候才真正创建对象

单例模式只生成一个实例,减少了系统性能的开销,当一个对象的产生需要较多资源时,比如读配置、产生其他依赖对象时,可以通过在启动时直接产生一个对象,然后永久驻留内存

饿汉式

class Bank {
    //1.私有化构造器
    private Bank() {}
    //2.内部创建对象,此对象也必须是静态的,只有一份
    private static Bank instance = new Bank();
    //3.提供公共静态方法,返回类的实例
    public static Bank getInstance() {
        return instance;
    }
}

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.先声明当前类的对象,没有创建实例
    private Bank instance = null;
    //3.声明public、static的方法,返回当前对象
    public static getInstance() {
        if(instance == null) 
            instance = new Bank();
        return instance;
    }
}

public class singletonTest {
    public static void main(String[] args) {
        Bank bank1 = Bank.getInstance();
        Bank bank2 = Bank.getInstance();
        System.out.println(bank1 == bank2); //true
    }
}

main方法

  1. main() 方法是程序的入口,一个类只能用一个 main()
  2. main() 方法也是一个普通的静态方法,可以通过类去调用
  3. main() 的参数是一个 String 数组,可以作为与控制台的交互方式
// 运行一个字节码文件
java 类名 "str1" "str2" "str3"
// 这时 main(String[] args) 方法中的 args[0]、args[1]、args[2],分别存入了 "str1" "str2" "str3",可以在程序中使用
  1. 一个完整的方法定义
public static void main(String[] args) {
    // ...
}
// 权限修饰符
// 其他修饰符
// 返回值类型
// 方法名
// 参数列表
// 方法体

类的结构之四:代码块(初始化块)

代码块,类中用一个大括号括起来的代码,用于初始化类或者对象的信息

根据是否使用 static 修饰,分成静态代码块和非静态代码块,如果一个类中定义了多个代码块,则按照声明的顺序执行,且静态代码块先于非静态代码块执行

静态代码块

  1. 内部可以有输出语句
  2. 随着类的加载而执行,而且只执行一次
  3. 作用:初始化类的信息,比如静态属性
  4. 静态代码块内只能调用静态属性和方法

非静态代码块

  1. 内部可以有输出语句
  2. 随着对象的创建而执行,每创建一次,就执行一次
  3. 作用:可以在创建对象时,对对象的属性进行初始化
  4. 非静态代码块要先于构造器执行
public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        System.out.println(s.age);
        System.out.println(s.country);
        System.out.println(s.id);
        System.out.println(s.school);
    }
}

class Person {
    int age = 1;
    static String country = "a";

    {
        System.out.println("Person类的非静态代码块执行");
        age = 10;
    }


    static {
        System.out.println("Person类的静态代码块执行");
        country = "China";
    }


    Person (){
        System.out.println("Person类的空参构造器执行");
    }
}

class Student extends Person {
    int id;
    static String school;

    {
        System.out.println("Student类的非静态代码块执行");
    }

    static {
        System.out.println("Student类的静态代码块执行");
    }

    public Student() {
        System.out.println("Student类的空参构造器执行");
    }
}

输出结果

Person类的静态代码块执行
Student类的静态代码块执行
Person类的非静态代码块执行
Person类的空参构造器执行
Student类的非静态代码块执行
Student类的空参构造器执行
10
China
0
null

如果把 Person 类中对 age 的显示初始化的语句放在非静态代码块后面,则其输出的值是 1,也就是说代码块中的变量可以在其后定义

实例化子类对象各结构的加载过程

由父及子,静态先行

  1. 父类的静态结构
  2. 子类的静态结构
  3. 父类的非静态结构
  4. 父类的构造器
  5. 子类的非静态结构
  6. 子类的构造器

对类的属性赋值

  1. 默认初始化
  2. 显示初始化/代码块(由其声明顺序决定)
  3. 构造器
  4. 对象调用赋值

final 关键字

final 可以修饰类、方法、变量

  • final 修饰的 不可被继承,例如 String、StringBuffer、System
  • final 修饰的 方法 不可被重写,例如 Object 中的 getClass()
  • final 修饰的 变量 为常量
    • final 修饰的 成员变量,可以考虑显示初始化,代码块中初始化,构造器中初始化,但是方法不可以给 final 修饰的属性赋值,static final 修饰的属性即为全局常量
    • final 修饰的 局部变量,如果是方法体内部的变量,不可再修改,如果是形参,当调用此方法给形参赋值后,就只能在方法体内使用此参数,不可再重新赋值

abstract 关键字

abstract 可以修饰 方法

抽象类

  • 抽象类不可以被实例化
  • 抽象类中一定有构造器,便于子类实例化时使用
  • 开发中,都会提供抽象类的子类,让子类实例化,完成相关操作

抽象方法

  • 只有方法声明,没有方法体
  • 包含抽象方法的类,一定是抽象类,反之,抽象类可以没有抽象方法
  • 子类重写了父类中所有的抽象方法,子类才可实例化,若子类没有重写父类所有的抽象方法,子类也是抽象类,也需要用 abstract 修饰

注意

  • abstract 不可以修饰属性、构造器
  • abstract 不可以修饰 private 方法,因为子类不可见,无法被重写
  • abstract 不可以修饰 static 方法,final 方法、final 类,因为无法被重写、继承

接口 interface

接口和类是平行的概念,一个类只能继承一个父类,但是可以实现多个接口,接口可以理解为规范,契约,是一组规则,实现 “能不能” 的关系(类之间是 “是不是” 的关系),体现为 “如果你是/要...则必须能...”

接口的使用

  • 使用 interface 关键字定义接口
  • 接口中的成员
    • JDK7 及以前:接口中只能定义全局常量和抽象方法
      • 全局常量:public static final
      • 抽象方法:public abstract
    • JDK8:除了定义全局常量和抽象方法,还可定义静态方法、默认方法(使用 default 修饰)
  • 接口中不可以定义构造器,接口不可以实例化
  • 通过类实现 implements 接口,如果实现类实现了接口中的所有抽象方法,则此实现类可以实例化,否则仍然是抽象类
interface Flyable {
    // 全局常量
    public static final int MAX_SPEED = 7900;
    //省略 public static final 的全局常量
    int MIN_SPEED = 1;

    // 抽象方法
    public abstract void fly();
    // 省略public abstract
    void stop();
}

class plane implements Flyable {
    @Override
    public void fly() {
        // ...
    }
    @Override
    public void stop() {
        // ...
    }
}
  • 一个类可以实现多个接口,弥补了单继承的局限性
class AA extends BB implements CC,DD,EE
  • 接口与接口之间可以继承,且可多继承
interface AA extends BB,CC
// 若要实现接口AA,则也要实现从BB,CC继承来的所有抽象方法
  • 接口的具体使用,就是多态性的体现,使用接口必须 new 其实现类的对象
public class USBTest {
    public static void main(String[] args) {
        Computer computer = new Computer();
        // 实参是实现了该接口的实现类的对象
        computer.dataTransport(new Flash());
    }
}

class Computer {
    // 形参是一个接口
    public void dataTransport(USB usb) {
        usb.start();
        System.out.println("数据传输中");
        usb.end();
    }
}

class Printer implements USB {
    @Override
    public void start() {
        System.out.println("打印机启动工作");
    }
    @Override
    public void end() {
        System.out.println("打印机结束工作");
    }
}

class Flash implements USB {
    @Override
    public void start() {
        System.out.println("U盘启动工作");
    }
    @Override
    public void end() {
        System.out.println("U盘结束工作");
    }
}

interface USB {
    // 全局静态常量
    public static final int SIZE = 100;
    // 抽象方法
    public abstract void start();
    public abstract void end();
}

Java8 接口新特性

Java8 中,接口中除了定义全局常量和抽象方法之外,还可以定义静态方法,默认方法

  1. 接口中定义的静态方法,只能通过接口来调,实现类不可重写
  2. 实现类的对象可以调用接口中的默认方法
  3. 实现类可以重写接口中的默认方法
  4. 如果实现类继承的父类和实现的接口中声明了同名同参的方法,则子类在没有重写此方法的前提下,默认调用父类中的该方法(类优先原则)

类的结构之五:内部类

  • Java 中允许将一个类 A 声明在另一个类B中,则类 A 就是内部类,类 B 称为外部类
  • 内部类可以分为成员内部类(静态、非静态)和局部内部类(定义在方法内、代码块内、构造器内)
  • 理解内部类
    • 内部类作为外部类的成员
      • 调用外部类的结构
      • 可以被 static 修饰
      • 可以被 4 种不同的权限修饰
    • 内部类作为一个类
      • 类内可以定义属性、方法、构造器等
      • 可以被 final 修饰,表示此类不能被继承
      • 可以被 abstract 修饰

非静态成员内部类

  • 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)
  • 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问
class Person{
    String name = "小明";
    public void eat(){}
    //非静态成员内部类
	class Head {
		String name = "脑袋";
		public void display(String name){
			System.out.println(name);//方法的形参
			System.out.println(this.name);//内部类的属性
			System.out.println(Person.this.name);//外部类的属性
		    Person.this.eat(); // 外部类的方法
		}
	}
}
  • 在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
  • 成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象,这和静态内部类不同

静态内部类

静态内部类是不需要依赖于外部类的,并且它不能使用外部类的非 static 的成员变量或者方法,创建静态内部类的对象不需要存在外部类的对象

public class Person {
    public String name;
    public int age;

    // 静态内部类
    public static class Head {
        public void think() {
            System.out.println("脑袋思考");
        }

    }

    // 非静态内部类
    public class Heart {
        public void beat() {
            System.out.println("心脏跳动");
        }
    }

    public void headThink() {
        // 外部类调用内部类结构必须有内部类对象
        new Head().think();
    }
}

public class InnerClassTest {
    public static void main(String[] args) {
        // 创建静态内部类对象不需要外部类对象,直接类名调用
        Person.Head head = new Person.Head();
        head.think();

        // 创建非静态内部类对象需要外部类对象
        //Person.Heart heart = new Person.Heart();
        Person p = new Person();
        Person.Heart heart = p.new Heart();
        heart.beat();
    }
}

局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内

class People{
    public People(){}
}
 
class Man{
    public Man(){}
     
    public People getWoman(){
        //局部内部类
        class Woman extends People{   
            int age =0;
        }
        return new Woman();
    }
}

注意:局部内部类的方法中的局部变量,必须声明为 final 的,JDK8 可以省略final,但这个变量依然是 final 的

匿名内部类

匿名内部类就是没有名字的内部类,匿名内部类必须重写或实现其抽象父类或接口的所有抽象方法。

public class InnerClassTest {
    public static void main(String[] args) {
        // 抽象类或接口并不能直接new对象,所以直接在调用处实现其全部抽象方法
        new Fly() {
            public void fly() {
                System.out.println("I can fly");
            }
        }.fly();
    }
}
public interface Fly {
    public abstract void fly();
}

其他规则:

  • 匿名内部类不能定义任何静态成员、方法
  • 匿名内部类中的方法不能是抽象的
  • 匿名内部类不能定义构造器;
  • 匿名内部类访问的外部类成员变量或成员方法必须用 static 的

内部类的编译

成员内部类和局部内部类,在编译以后,都会生成字节码文件。

  • 成员内部类:外部类$内部类名.class
  • 局部内部类:外部类$数字 内部类名.class