Java基础day13 ——代码块、抽象、final、设计模式及初始化

本文详细介绍了Java中的类成员(静态代码块、非静态代码块、构造器)的作用、执行顺序以及多态和抽象的概念。还探讨了设计模式中的单例模式实现(饿汉式和懒汉式),以及对象的初始化过程和原则。
摘要由CSDN通过智能技术生成

1.类的成员之代码块

静态代码块 static 优先运行 只运行一次 按顺序从上向下 优先级高(所以不能调用优先级低的非静态代码块)

非静态代码块  优先于构造器运行 从上向下 顺序 每构造一次就调用一次

构造器: 可传参 多个构造器时不一定调用

package com.atguigu.java;

/*
`   类的成员之代码块

    作用:对类或对象进行初始化。

    格式:{}

    代码块的分类 :静态代码块 vs 非静态代码块

    静态代码块:
        1.作用:用来对类进行初始化(在静态代码块中用来放在类加载的过程中只执行一次的代码)
        2.静态代码块可以有多个从上向下依次执行。
        3.静态代码块是随着类的加载而加载(执行)类加载只加载一次。
        4.不能调用非静态的-因为加载时机不同
            可以调用静态的
        5.静态代码块的执行先于非静态代码块的执行

    非静态代码块:
        1.作用:用来对对象进行初始化(在对象中只执行一次的代码可以放在非静态代码块中执行)
        2.每创建一次对象就会调用一次非静态代码块
        3.非静态代码块的执行先于构造器。
        4.如果有多个非静态代码块从上向下依次执行。
        5.非静态代码块可以调用类变量和类方法(当创建对象时已经类加载过了)。


    构造器也可以初始化(初始化怎么选)。
        构造器和静态代码块怎么选? 构造器是每创建一次对象就会调用一次。静态代码块是随着类的加载而加载只加载一次。
        构造器和非静态代码块怎么选? ①构造器可以传参-非静态代码块不可以传参 ② 构造器有多个的情况下不一定调用哪个但是非静态代码块一定会调用
 */

class Person{
    int id = 5;
    static int sid = 6;

    /*
        非静态代码块
     */
    {
        System.out.println("非静态代码块");
        System.out.println(sid);//可以调用静态的
    }
    {
        System.out.println("非静态代码块2");
    }
    {
        System.out.println("非静态代码块3");
    }

    //注意块:只能被static修饰
    static {
        //静态代码块
        System.out.println("静态代码块");
        //System.out.println(id); 不能调用非静态的-因为加载时机不同
        System.out.println(sid);//可以调用静态的
        run();//可以调用静态的
    }

    static {
        //静态代码块
        System.out.println("静态代码块2");
    }

    public Person(){
        System.out.println("构造器");
    }

    public void test(){

    }

    public static void run(){
        System.out.println("run");
    }
}
public class BlockTest {
    public static void main(String[] args) {

        new Person();
        System.out.println("========================");
        new Person();
//        Person.run();
//        Person.run();
    }
}

例子

初始化只进行一次

方法中初始化会导致重复初始化 

package com.atguigu.java;


import java.lang.reflect.Array;
import java.util.Arrays;

/*
    所有的员工都可以录入商品名称(该商品的名称要对所有人可见)
        注意:所有商品的信息 所有对象共享
 */
class Employee{
    static String[] names ;

    //静态代码块只是在类加载的时候执行一次
    static {
        names = new String[5];
    }

    {

    }
    /*
    在构造器中对names进行初始化  那么如果创建多次对象就会初始化多次后一次会覆盖前一次
    public Employee(int length){
        names = new String[5];
    }
     */

    static int i = 0;

    /*
    不能在方法中初始化(因为初始化是一定要做的 而且只能做一次)
    public static void initArray(){
        names = new String[5];
    }
     */

    public void setName(String skuName){
        names[i++] = skuName;
    }

    public String[] getNames(){
        return names;
    }
}
public class BlockTest2 {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        e1.setName("方便面");

        Employee e2 = new Employee();
        e2.setName("矿泉水");

        System.out.println(Arrays.toString(e1.getNames()));
        System.out.println(Arrays.toString(e2.getNames()));
    }
}

2.多态的虚方法


/*
    多态

    虚方法:可以被子类重写的方法叫作虚方法。
       不是虚方法(类方法,final修饰的方法-不能被重写   private修饰的方法 如果跨包继承缺省的方法)

    静态绑定:在编译时就可以确定调用哪些方法(不是虚方法)。


    动态绑定技术(编译看左边 运行看右边):在编译时调用哪个方法并不能确定 在运行时再确定调用哪个方法。
        编译时静态分派:先看这个对象xx的编译时类型,在这个对象的编译时类型中找到能匹配的方法
            匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
            A:找最匹配          实参的编译时类型 = 方法形参的类型
            B:找兼容(多态)      实参的编译时类型 < 方法形参的类型

        运行时动态绑定:再看这个对象xx的运行时类型,如果这个对象xx的运行时类重写了刚刚找到的那个匹配的方法,
            那么执行重写的,否则仍然执行刚才编译时类型中的那个匹配的方法

    思考:如果调用方法都是动态绑定效率高吗?不好-效率低
            如何解决:只有调用虚方法时才采用动态绑定技术
 */
public class PolymorphicTest {
}

3.抽象

抽象类一般用abstra 修饰 

不能被实例化即(方法不用{})

类中不一定有抽象方法 但有一定在抽象类中

抽象方法必须被子类实现

package com.atguigu.java2;

/*
    关键字: abstract(抽象)

    abstract修饰类:抽象类
        1.抽象类不能被实例化。
        2.抽象方法所在的类必须为抽象类。
        3.抽象类中不一定有抽象方法
        4.抽象类中有构造器(抽象类就是一个类只不过该类不能被实例化)

    abstract修饰方法 :抽象方法
        1.抽象方法没有方法体。
        2.抽象方法必须被子类实现。

    注意:
        1.抽象类的子类必须实现父类中所有的抽象方法。
            如果抽象方法被父类已经实现那么子类不用再实现该抽象方法。
        2.抽像类中不一定有抽象方法但抽象方法一定在抽象类中。
 */

abstract class A{
    public abstract void show();
}

abstract class Animal extends A{ //抽象方法所在的类必须为抽象类 --- 抽象类不能实例化

    {

    }

    public Animal(){

    }

    public abstract void say();//抽象方法--可以没有方法体

    public abstract void run();

    public void eat(){

    }

    //抽象子类也可以实现抽象父类中的抽象方法
    @Override
    public void show() {

    }
}
class Dog extends Animal{//继承抽象类后要实现抽象类中所有的抽象方法
    public void say(){
        System.out.println("旺 旺 旺......");
    }

    @Override
    public void run() {
        System.out.println("狗跑起来了.....");
    }


}

public class AbstractTest {
    public static void main(String[] args) {
        //为什么不让抽象类创建对象--- 因为没意义

    }
}

设计模式

是一种解决特殊问题的思路(目前了解)

package com.atguigu.java2;

/*
    设计模式:用来解决特殊问题(结构层面)的思路
        常见的设计模式有23种设计模式(单例设计模式,模板设计模式,代理设计模式,装饰设计模式,工厂设计模式...)

    模板设计模式:定义一个操作的固定的算法架构,并允许子类在不改变结构的前提下通过实现特定步骤从而实复用和扩展。

    注意:在实际开发中大多数情况下不是一开始就知道要用什么设计模式或者哪些类作为父类和子类。
            而是在不断的写过程中发现总结然后对代码进行修改甚至重构。

    案例: 计算输出10000以内偶数所需要的时间
    案例 :计算输出10000以内奇数所需要的时间
 */
abstract class Computer{
    public void run(){
        //this ---- 指向的是子类对象
        //1.获取当前时间 - 开始执行前的时间
        //currentTimeMillis() : 获取当前时间到1970年1月1日的毫秒数
        long startTime = System.currentTimeMillis();
        //2.执行代码
        this.runCode();
        //3.获取当前时间 - 执行后的时间
        long endTime = System.currentTimeMillis();
        //4.计算时间差
        System.out.println("用时:" + (endTime - startTime));
    }

    public abstract void runCode();
}

/*
    输出奇数
 */
class ComputerOddNumber extends Computer{
    @Override
    public void runCode() {
        for (int i = 0; i <= 10000 ; i++) {
            if (i % 2 != 0) {
                System.out.println(i);
            }
        }
    }
}

/*
    输出偶数
 */
class ComputerEvenNumber extends Computer{
    @Override
    public void runCode() {
        for (int i = 0; i <= 10000 ; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}



public class AbstractTest2 {

    public static void main(String[] args) {

        Computer c = new ComputerOddNumber();//多态
        c.run();
    }
}

饿汉式与懒汉式(单例设计模式)

饿汉式 在类中创建对象 类外通过方法调用对象

懒汉式 调用的时候再在类中创建对象并通过方法调用

package com.atguigu.java3;

/*
    单例设计模式:在程序运行的过程中某一个类的对象只能存在一个。

    单例设计模式的实现 : 饿汉式 vs 懒汉式

    饿汉式:线程安全(后面讲),在我们调用方法之前该类的对象就已经创建好了-浪费内存

    懒汉式:线程不安全(后面讲),在我们调用方法的时候再创建对象(延迟加载-懒加载) - 一定程序上节省了内存
 */

//懒汉式
class Bank{
    //私有化构造器-不让在类的外边调用该构造器
    private Bank(){}

    //声明一个本类类型的属性
    private static Bank bank = null;

    //通过方法获取类中的对象 - 因为类的外面无法创建本类对象所以就无法调用本类中的实例方法
    public static Bank getInstance(){ //什么时候调用此方法 什么时候创建对象
        if (bank == null){
            bank = new Bank();
        }
        return bank;
    }
}

//饿汉式
class Employee{
    //私有化构造器-不让在类的外边调用该构造器
    private Employee(){}

    //创建一个本类的对象 - 因为要通过类方法调用此类变量。
    private static final Employee e = new Employee();

    //通过方法获取类中的对象 - 因为类的外面无法创建本类对象所以就无法调用本类中的实例方法
    public static Employee getInstance(){
        return e;
    }
}


public class SingletonTest {
    public static void main(String[] args) {

        Employee e = Employee.getInstance();
        Employee e2 = Employee.getInstance();
        System.out.println(e == e2);

        System.out.println("====================");
//        Bank b1 = Bank.getInstance();
//        Bank b2 = Bank.getInstance();
//
//        System.out.println(b1 == b2);

        for (int i = 0; i < 500; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Bank.getInstance());
                }
            }).start();
        }


    }
}

练习

package com.atguigu.java2;

/*
(1)声明抽象父类Light灯,包含抽象方法 void on()和void off()
(2)声明台灯Lamp,继承Light,重写on抽象方法:
    输出  台灯打开,灯常亮,重写off抽象方法:输出台灯关闭
(3)声明转向灯TurnSignal,继承继承Light,重写on抽象方法:
    输出 转向灯打开,闪烁亮,重写off抽象方法:输出转向灯关闭
(4)测试类,创建两个子类对象,并调用on和off方法。
 */
abstract class Light{
    public abstract void on();
    public abstract void off();
}

class TurnSignal extends Light{

    @Override
    public void on() {
        System.out.println("转向灯打开,闪烁亮");
    }

    @Override
    public void off() {
        System.out.println("转向灯关闭");
    }
}

class Lamp extends Light{

    @Override
    public void on() {
        System.out.println("台灯打开,灯常亮");
    }

    @Override
    public void off() {
        System.out.println("台灯关闭");
    }
}
public class AbstractTest3 {
    public static void main(String[] args) {
        run(new TurnSignal());
        run(new Lamp());
    }

    public static void run(Light l){//多态
        l.on();
        System.out.println("===========");
        l.off();
    }
}

abstract 和关键字的适配性

final static(静态) private native(方法体实现非java语言)

package com.atguigu.java2;

/*
    abstract和哪些关键字不能一起使用?

    abstract和final不能一起修饰方法和类。
    abstract和static不能一起修饰方法。
    abstract和private不能一起修饰方法。
    abstract和native不能一起修饰方法 (native修饰的方法没有方法体但不是抽象方法 该方法的实现是用其它语言实现的)
 */
public class AbstractTest4 {

    //public abstract static void say();
//    private abstract void say();

}

4.关键字final

修饰类:不能被继承

修饰方法: 不能被重写

修饰变量 : 不能被修改

修饰的属性:必须赋值

赋值可直接赋值 也可代码块赋值 也可构造器赋值(前提是不出现构造器不被调用导致无法赋值的情况)

package com.atguigu.java3;

/*
    final关键字

    [面试题]请简述final关键字

    final修饰类(最终的类)-太监类:该类不能被继承。(比如:String StringBuilder,....)
    final修饰方法(最终的方法):不能被重写
    final修饰的变量 :值不能被修改

    final修饰的属性:没有默认值要求必须赋值
        赋值的方式 :①显示赋值 ②代码块赋值 ③构造器赋值-一定要保证无论调用哪个构造器都可以给该变量赋值
 */
class Person{
    final int id; //直接给属性赋值 - 显示赋值

    {
        //id = 10; //代码块赋值
    }

    public Person(){
        id = 20;
    }

    public Person(int age){
        id = 23;
    }

    public void test(){
        //因为通过方法给final修饰的变量进行赋值 无法保证该方法一定会被调用所以不能通过方法给final修饰的变量赋值
        //id= 20;
    }
}
class Man extends Person{
    @Override
    public void test() {
        super.test();
    }
}
public class FinalTest {
}

5.对象的初始化

类的初始化

类只初始化一次

故第二次创建对象时不会再次创建类

package com.atguigu.java4;

/*
    类的初始化过程 : 就是给类变量赋值的过程

    哪些操作会初发类的初始化:
        1.创建对象 (先有类再有对象)
        2.调用类中的静态资源

    说明:
        1.对子类进行初始化时会先对父类进行类的初始化
        2.类的初始化只执行一次
        3.通过子类调用父类的静态资源时不会对子类进行类的初始化
        4.当我们进行类的初始化时底层会调用一个clinit方法(该方法的数量和有几个父类没有关系-只有一个)
            在clinit方法中将父类还有子类中的初始化操作全部合并到clinit方法中执行。
         注意:静态代码块和类变量谁在上面先执行谁。
 */
class SuperA{

//    public void setName(String name){
//        this.name = name;
//    }
//    String name;

    static int id = 5;

    static {
        id = 10;
    }
}
class SubA extends SuperA{

    static int sid = 12;

    static {
        sid = 20;
    }

}
public class ClassInitTest {
    public static void main(String[] args) {
        int id = SubA.sid;
        System.out.println(id);

        //类的初始化只初始化一次
//        new SubA();
//        new SubA();

        //通过子类调用父类的静态资源
        //调用类中的类变量和类方法 -- 最正确的做法就是用那个类的类名去调(我们推测-底层是用父类的类名调用的父类中的类变量)
//        int id = SubA.id;
    }
}

类与对象初始化的对比

类只初始化一次

对象是创建一次初始一次

package com.atguigu.java4;


class Animal{

    int aid = 10;

    static int said = 20;

    static {
        System.out.println("animal static block1");
    }

    {
        System.out.println("animal block");
    }

    static {
        System.out.println("animal static block2");
    }

    public Animal(){
        System.out.println("animal 构造器");
    }
}

class Dog extends Animal{
    int did = 10;

    static int sdid = 20;

    static {
        System.out.println("Dog static block1");
    }

    {
        System.out.println("Dog block");
    }

    static {
        System.out.println("Dog static block2");
    }

    public Dog(){
        System.out.println("dog 构造器");
    }
}
public class ClassObjectInitTest {
    public static void main(String[] args) {
        /*
            先是类的初始化 再是对象的初始化
            类的初始化只执行一次 对象的初始化每创建一次对象执行一次。
         */
        new Dog();
        new Dog();
    }
}

对象的初始化

赋值的过程

顺序

父类优先于子类

静态优先于非静态优先于构造器

继承中每个类有一个init方法

package com.atguigu.java4;

/*
    对象的初始化过程--对属性赋值的过程

    说明:
        1.在对子类的对象进行初始化时必先对父类进行初始化。
        2.初始化的过程(从子类的构造器到父类的构造器再到下面的流程):
            父类
                属性、代码块(谁在上谁先) ,构造器
            子类
                属性、代码块(谁在上谁先) ,构造器
       3.在继承关系中每个类都会对应一个init方法。
       4.init方法:在程序的执行过程中 会将属性,代码块,构造器的操作全部合并到init方法中执行。
            注意:前两个(属性和代码块)看顺序 最后一定是构造器。

    注意:
        1.每创建一次对象都会执行一次对象的初始化过程
 */
class SuperClass{
    int sid = 5;

    {
        sid = 20;
    }

    public SuperClass(){
        sid = 30;
    }
}
class SubClass extends SuperClass{
    int id = 10;//显示赋值

    {
        id = 20;//代码块赋值
    }

    public SubClass(){
        id = 30;//构造器赋值
    }
}
public class ObjectInitTest {
    public static void main(String[] args) {
//        SubClass sb = new SubClass();
//        int id = sb.id;
//        System.out.println(id);

        new SubClass();
        new SubClass();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值