Java进阶-面向对象

本文章参考B站 Java入门基础视频教程,java零基础自学首选黑马程序员Java入门教程(含Java项目和Java真题),仅供个人学习使用,部分内容为本人自己见解,与黑马程序员无关。

1、面向对象编程 (Object Oriented Programming)

1.1、面向对象

简单来说,面向对象就是找东西(找对象),设计东西(设计对象)过来解决问题(编程)

在这里插入图片描述

在这里插入图片描述

代码演示

public class Car {
    // 属性(成员变量)
    String name; // 名称
    double price; // 价格

    // 行为(方法)
    public void start(){
        System.out.println(name + " 价格是:" + price +", 启动成功!");
    }

    public void run(){
        System.out.println(name + " 价格是:" + price +", 跑的很快!");
    }
}
/**
   目标:掌握自己设计类,并获得对象。
 */
public class Test {
    public static void main(String[] args) {
        // 如何去获取汽车的对象。
        Car c1 = new Car();
        System.out.println(c1);
        c1.name = "宝马5系";
        c1.price = 37.89;
        System.out.println(c1.name);
        System.out.println(c1.price);
        c1.start();
        c1.run();

        System.out.println("-------------------");
        Car c2 = new Car();
        System.out.println(c2);
        c2.name = "奔驰E系";
        c2.price = 39.89;
        System.out.println(c2.name);
        System.out.println(c2.price);
        c2.start();
        c2.run();

    }
}

输出结果:

com.itheima.createobject.Car@1b6d3586
宝马5系
37.89
宝马5系 价格是:37.89, 启动成功!
宝马5系 价格是:37.89, 跑的很快!
-------------------
com.itheima.createobject.Car@4554617c
奔驰E系
39.89
奔驰E系 价格是:39.89, 启动成功!
奔驰E系 价格是:39.89, 跑的很快!

1.2、对象内存图

多个对象的内存图

在这里插入图片描述

  1. 对象放在哪个位置?
  • 堆内存中
  1. Car c = new Car(); c变量名中存储的是什么?
  • 存储的是对象在堆内存中的地址。
  1. 成员变量(name、price)的数据放在哪里,存在于哪个位置?
  • 对象中,存在于堆内存中。

代码演示

public class Car {
    // 属性(成员变量)
    String name; // 名称
    double price; // 价格

    // 行为(方法)
    public void start(){
        System.out.println(name + " 价格是:" + price +", 启动成功!");
    }

    public void run(){
        System.out.println(name + " 价格是:" + price +", 跑的很快!");
    }
}
/**
   目标:掌握自己设计类,并获得对象。
 */
public class Test {
    public static void main(String[] args) {
        // 如何去获取汽车的对象。
        Car c1 = new Car();
        System.out.println(c1);
        c1.name = "宝马X3";
        c1.price = 37.89;
        System.out.println(c1.name);
        System.out.println(c1.price);
        c1.start();
        c1.run();

        System.out.println("-------------------");
        Car c2 = new Car();
        System.out.println(c2);
        c2.name = "奔驰GLC";
        c2.price = 39.89;
        System.out.println(c2.name);
        System.out.println(c2.price);
        c2.start();
        c2.run();

    }
}

输出结果:

com.itheima.createobject.Car@1b6d3586
宝马X3
37.89
宝马X3 价格是:37.89, 启动成功!
宝马X3 价格是:37.89, 跑的很快!
-------------------
com.itheima.createobject.Car@4554617c
奔驰GLC
39.89
奔驰GLC 价格是:39.89, 启动成功!
奔驰GLC 价格是:39.89, 跑的很快!

两个变量指向同一个对象的内存图

在这里插入图片描述

代码演示

public class Student {
    String name;
    char sex;
    String hobby;

    public void study(){
        System.out.println("姓名:" + name +",性别是:" + sex
                + ",爱好是:" + hobby + "的学生在好好学习,天天向上!");
    }
}
public class Test {
    public static void main(String[] args) {
        // 目标:掌握2个变量指向同一个对象的形式
        Student s1 = new Student();
        s1.name = "小明";
        s1.sex = '男';
        s1.hobby = "睡觉、游戏、听课";
        s1.study();

        // 关键:把s1赋值给学生类型的变量s2
        Student s2 =  s1;
        System.out.println(s1);
        System.out.println(s2);

        s2.hobby = "爱提问";

        System.out.println(s2.name);
        System.out.println(s2.sex);
        System.out.println(s1.hobby);
        s2.study();

        s1 = null;
        s2 = null;
        System.out.println(s1.name);
    }
}

输出结果:注意 s1.hobby 已经发生变化

姓名:小明,性别是:男,爱好是:睡觉、游戏、听课的学生在好好学习,天天向上!
com.itheima.memory.Student@1b6d3586
com.itheima.memory.Student@1b6d3586
小明
男
爱提问
姓名:小明,性别是:男,爱好是:爱提问的学生在好好学习,天天向上!
Exception in thread "main" java.lang.NullPointerException
	at com.itheima.memory.Test.main(Test.java:26)

Process finished with exit code 1

1.3、初探数组与集合

数组Array和集合的区别:

  1. 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型)
  2. JAVA集合可以存储和操作数目不固定的一组数据。
  3. 若程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。

补充:对象数组里面的每一个对象的值也是引用地址

1.4、构造器

通过构造器可以创建对象

在这里插入图片描述

代码演示

public class Car {
    String name;
    double price;

    /**
      无参数构造器(默认存在的)
     */
    public Car(){
        System.out.println("无参数构造器被触发执行~~~");
    }

    /**
       有参数构造器
     */
    public Car(String n, double p){
        System.out.println("有参数构造器被触发执行~~~");
        name = n;
        price = p;
    }
}
/**
    目标:明白构造器的作用和分类。(开发的人,理解能力好)
 */
public class ConstructorDemo {
    public static void main(String[] args) {
        Car c = new Car();
//        c.name = "";
//        c.price
        System.out.println(c.name);
        System.out.println(c.price);

        Car c2 = new Car("奔驰GLC", 39.78);
        System.out.println(c2.name);
        System.out.println(c2.price);
    }
}

输出结果:

无参数构造器被触发执行~~~
null
0.0
有参数构造器被触发执行~~~
奔驰GLC
39.78

1.5、this关键字

在这里插入图片描述
this:代表当前对象

代码演示

package com.itheima.test;

public class Car {

    private String name;
    private double price;

    public Car() {
        System.out.println("无参构造器的this:" + this);
    }

    public void run() {
        System.out.println("方法run中的this:" + this);
    }

    public Car(String name, Double price) {
        System.out.println("有参构造器的this:" + this);
        this.name = name;
        this.price = price;
    }

    public void stop() {
        System.out.println("方法stop中的this:" + this);
    }
}
package com.itheima.test;

public class Test {

    public static void main(String[] args) {
        Car car = new Car();
        System.out.println("car对象的地址:" + car);
        car.run();

        Car car1 = new Car("宝马5系", 48.88);
        System.out.println("car1对象的地址:" + car1);
        car1.stop();
    }

}

输出结果:

无参构造器的this:com.itheima.test.Car@1b6d3586
car对象的地址:com.itheima.test.Car@1b6d3586
方法run中的this:com.itheima.test.Car@1b6d3586
有参构造器的this:com.itheima.test.Car@4554617c
car1对象的地址:com.itheima.test.Car@4554617c
方法stop中的this:com.itheima.test.Car@4554617c

没使用this的情况

public class Car {

    String name;
    double price;

    public Car(String name, Double price) {
        name = name;
        price = price;
    }

}
public class Test {

    public static void main(String[] args) {
        Car car = new Car("宝马五系", 48.88);
        System.out.println(car.name);
        System.out.println(car.price);
    }

}

输出结果:

null
0.0

可以发现名字相同的话是就就近赋值,把传过来的值赋值给自己,并没有修改对象的属性值

加了 this 后效果演示

package com.itheima.test;

public class Car {

    String name;
    double price;

    public Car(String name, Double price) {
        this.name = name;
        this.price = price;
    }

}

package com.itheima.test;

public class Test {

    public static void main(String[] args) {
        Car car = new Car("宝马五系", 48.88);
        System.out.println(car.name);
        System.out.println(car.price);
    }

}

输出结果:

宝马五系
48.88

this应用

public class Car {

    String name;
    double price;

    public Car(String name, Double price) {
        this.name = name;
        this.price = price;
    }

    public void runWith(String name) {
        System.out.println(name + "和" + name + "在比赛");
        System.out.println(this.name + "和" + name + "在比赛");
    }

}
public class Test {

    public static void main(String[] args) {
        Car car = new Car("宝马五系", 48.88);

        car.runWith("奔驰E系");
    }

}
奔驰E系和奔驰E系在比赛
宝马五系和奔驰E系在比赛

1.6、封装

面向对象三大特征:封装、继承、多态
在这里插入图片描述
对象代表什么,就得封装对应的数据,并提供数据对应的行为

  • 人画圆,画圆的方法应该在圆对象里面 (圆是圆自己画出来的,我们只是调用了画圆的方法)
  • 人关门,关门的方法应该在门对象里面(门是门自己关的,我们只是提供了作用力,相当于调用了关门的方法)

封装就是指将成员变量私有,提供方法暴露

封装的好处演示

public class Student {
    // private私有的成员变量,只能在本类访问。
   private int age;

   public int getAge(){
       return age;
   }

   public void setAge(int age){
        if(age >= 0 && age <= 200){
            this.age = age;
        }else {
            System.out.println("年龄数据有问题,应该不是人的年龄!");
        }
   }
}
/**
    目标:学会面向对象的三大特征:封装的形式、作用。
 */
public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        // s.age = -23;
        s.setAge(-23);
        System.out.println(s.getAge());
    }
}

输出结果:

年龄数据有问题,应该不是人的年龄!
0

1.7、JavaBean

在这里插入图片描述

1.8、成员变量、局部变量区别

在这里插入图片描述

在这里插入图片描述

2、常用API (String、ArrayList)

2.1、String

String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!

String 对象创建出来是不会改变的

2.1.1、创建 String 字符串的方式

在这里插入图片描述
在这里插入图片描述

代码演示

/**
    目标:String类创建字符串对象的2种方式
 */
public class StringDemo2 {
    public static void main(String[] args) {
        // 方式一:直接使用双引号得到字符串对象
        String name = "我爱你中国";
        System.out.println(name);

        // 方式二:
        // 1、public String(): 创建一个空白字符串对象,不含有任何内容 (几乎不用)
        String s1 = new String(); // s1 = ""
        System.out.println(s1);

        // 2、public String(String): 根据传入的字符串内容,来创建字符串对象(几乎不用)
        String s2 = new String("我是中国人");
        System.out.println(s2);

        // 3、public String(char[] c): 根据字符数组的内容,来创建字符串对象
        char[] chars = {'a' , 'b' , '中', '国'};
        String s3 = new String(chars);
        System.out.println(s3);

        // 4、public String(byte[] b):  根据字节数组的内容,来创建字符串对象
        byte[] bytes = {97, 98, 99, 65, 66, 67};
        String s4 = new String(bytes);
        System.out.println(s4);

        System.out.println("---------------------------------------");
        String ss1 = "abc";
        String ss2 = "abc";
        System.out.println(ss1 == ss2);

        char[] chars1 = {'a' , 'b' , 'c'};
        String ss3 = new String(chars1);
        String ss4 = new String(chars1);
        System.out.println(ss3 == ss4);
    }
}

输出结果:

我爱你中国

我是中国人
ab中国
abcABC
---------------------------------------
true
false

2.1.2、创建 String 字符串的内存原理 (常见面试题)

通过 “” 定义字符串内存原理

在这里插入图片描述

通过new构造器得到字符串对象内存原理

在这里插入图片描述

2.1.3、Java String 面试题

在这里插入图片描述
在这里插入图片描述
Java存在编译优化机制,程序在编译时: “a” + “b” + “c” 会直接转成 “abc”,原理可见编译后的 Class 文件

在这里插入图片描述

总结

总之,记住只有从常量池中取的才是相同的

2.1.4、String equals() 方法

字符串比较用 “equals” 比较,不能直接用 “",因为 "” 是判断地址的

在这里插入图片描述

  • String xx = “xx” 在常量池中定义

  • 用变量接收的字符串则在堆内存中

在这里插入图片描述

代码演示

public class StringAPIEqualsDemo4 {
    public static void main(String[] args) {
        // 1、正确登录名和密码
        String okName = "itheima";
        String okPassword = "123456";

        // 2、请您输入登录名称和密码
        Scanner sc = new Scanner(System.in);
        System.out.println("登录名称:");
        String name = sc.next();
        System.out.println("登录密码:");
        String password = sc.next();

        // 3、判断用户输入的登录名称和密码与正确的内容是否相等。
        if(okName.equals(name) && okPassword.equals(password)){
            System.out.println("登录成功!");
        }else {
            System.out.println("用户名或者密码错误了!");
        }

        // 4、忽略大小写比较内容的Api: 一般用于比较验证码这样的业务逻辑
        String sysCode = "23AdFh";
        String code1 = "23aDfH";
        System.out.println(sysCode.equals(code1)); // false
        System.out.println(sysCode.equalsIgnoreCase(code1)); // true
    }
}

输出结果:

登录名称:
itheima
登录密码:
123456
登录成功!
false
true

拓展

基本类型比较才用 “==”

2.1.4、String 类常用方法

在这里插入图片描述

代码演示

/**
    目标:掌握String常用的其他API。
 */
public class StringAPIOtherDemo5 {
    public static void main(String[] args) {
        // 1、public int length(): 获取字符串的长度
        String name = "我爱你中国love";
        System.out.println(name.length());

        // 2、public char charAt(int index): 获取某个索引位置处的字符
        char c = name.charAt(1);
        System.out.println(c);

        System.out.println("------------遍历字符串中的每个字符--------------");
        for (int i = 0; i < name.length(); i++) {
            char ch = name.charAt(i);
            System.out.print(ch + " ");
        }
        System.out.println();

        System.out.println("------------字符串转换成字符数组--------------");
        // 3、public char[] toCharArray():: 把字符串转换成字符数组
        char[] chars = name.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char ch = chars[i];
            System.out.print(ch + " ");
        }
        System.out.println();

        System.out.println("------------字符串截取内容--------------");
        // 4、public String substring(int beginIndex, int endIndex) :截取内容,(包前不包后的)
        String name2 = "Java是最厉害的编程语言!";
        //              01234567   89
        String rs = name2.substring(0, 9);
        System.out.println(rs);
        String rs1 = name2.substring(4, 9);
        System.out.println(rs1);

        // 5、public String substring(int beginIndex):从当前索引一直截取到末尾
        String rs2 = name2.substring(4);
        System.out.println(rs2);

        // 6、public String replace(CharSequence target, CharSequence replacement)
        String name3 = "Chovy是最厉害的90后,Chovy棒棒的!我好爱Chovy";
        String rs3 = name3.replace("Chovy", "***");
        System.out.println(rs3);

        // 7、public boolean contains(CharSequence s)
        System.out.println(name3.contains("Chovy")); // true
        System.out.println(name3.contains("Chovy1")); // false

        // 8、public boolean startsWith(String prefix)
        System.out.println(name3.startsWith("Chovy"));
        System.out.println(name3.startsWith("Chovy是最厉害的"));
        System.out.println(name3.startsWith("Chovy是最厉害的2"));

        // 9、public String[] split(String s): 按照某个内容把字符串分割成字符串数组返回。
        String name4 = "IU,TaylorSwift,Krystal";
        String[] names = name4.split(",");
        for (int i = 0; i < names.length; i++) {
            System.out.println("选择了:" + names[i]);
        }
    }
}

输出结果:

9
爱
------------遍历字符串中的每个字符--------------
我 爱 你 中 国 l o v e 
------------字符串转换成字符数组--------------
我 爱 你 中 国 l o v e 
------------字符串截取内容--------------
Java是最厉害的
是最厉害的
是最厉害的编程语言!
***是最厉害的90后,***棒棒的!我好爱***
true
false
true
true
false
选择了:IU
选择了:TaylorSwift
选择了:Krystal

2.1.5、String 类使用案例

String类开发验证码功能

import java.util.Random;

/**
    练习题:使用String完成随机生成5位的验证码。
 */
public class StringExec6 {
    public static void main(String[] args) {
        // 1、定义可能出现的字符信息
        String datas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        
        // 2、循环5次,每次生成一个随机的索引,提取对应的字符连接起来即可
        String code = "";
        Random r = new Random();
        for (int i = 0; i < 5; i++) {
            // 随机一个索引
            int index = r.nextInt(datas.length());
            char c = datas.charAt(index);
            code += c;
        }

        // 3、输出字符串变量即可
        System.out.println(code);
    }
}

输出结果:

l6JUh

模拟用户登录功能

/**
    练习题:模拟用户登录
 */
public class StringExec7 {
    public static void main(String[] args) {
        // 1、定义正确的登录名称和密码
        String okLoginName = "admin";
        String okPassword = "itheima";

        // 2、定义一个循环,循环3次,让用户登录
        Scanner sc = new Scanner(System.in);
        for (int i = 1; i <= 3; i++) {
            System.out.println("请您输入登录名称:");
            String loginName = sc.next();
            System.out.println("请您输入登录密码:");
            String password = sc.next();

            // 3、判断登录是否成功!
            if(okLoginName.equals(loginName)){
                // 4、判断密码是否正确
                if(okPassword.equals(password)){
                    System.out.println("登录成功!欢迎进入系统随意浏览~~~~");
                    break;
                }else {
                    // 密码错误了
                    System.out.println("您的密码不正确!您还剩余" + (3 - i) +"次机会登录!");
                }
            }else {
                System.out.println("您的登录名称不正确!您还剩余" + (3 - i) +"次机会登录!");
            }

        }

    }

}

输出结果:

请您输入登录名称:
admin1
请您输入登录密码:
123456
您的登录名称不正确!您还剩余2次机会登录!
请您输入登录名称:
admin
请您输入登录密码:
123456
您的密码不正确!您还剩余1次机会登录!
请您输入登录名称:
admin
请您输入登录密码:
itheima
登录成功!欢迎进入系统随意浏览~~~~

手机号码屏蔽

import java.util.Scanner;

/**
    练习题:手机号码屏蔽
 */
public class StringExec8 {
    public static void main(String[] args) {
        // 1、键盘录入一个手机号码
        Scanner sc = new Scanner(System.in);
        System.out.println("请您输入您的手机号码:");
        String tel = sc.next();

        // 2、截取号码的前三位,后四位    18665666520
        String before = tel.substring(0, 3); // 0  1  2
        String after = tel.substring(7);  // 从索引7开始截取到手机号码的末尾

        String s = before + "****" + after;
        System.out.println(s);
    }
}

输出结果:

请您输入您的手机号码:
18665666520
186****6520

2.2、ArrayList

2.2.1、数组和 ArrayList的区别

1、数组和集合的元素存储的个数问题?

  • 数组定义后类型确定,长度固定
  • 集合类型可以不固定,大小是可变的。

2、数组和集合适合的场景

  • 数组适合做数据个数和类型确定的场景

  • 集合适合做数据个数不确定,且要做增删元素的场景

重点:数组跟 ArrayList 变量里面存的都是引用地址,只是获取的时候Java做了优化。

2.2.2、ArrayList 类入门 (构建ArrayList)

import java.util.ArrayList;

**
      目标: 创建ArrayList对象,代表集合容器,往里面添加元素。
 */
public class ArrayListDemo1 {
    public static void main(String[] args) {
        // 1、创建ArrayList集合的对象
        ArrayList list = new ArrayList();

        // 2、添加数据
        list.add("Java");
        list.add("Java");
        list.add("MySQL");
        list.add("黑马");
        list.add(23);
        list.add(23.5);
        list.add(false);
        System.out.println(list.add('中'));
        System.out.println(list);

        // 3、给指定索引位置插入元素
        list.add(1, "赵敏");
        System.out.println(list);
    }
}

输出结果:

true
[Java, Java, MySQL, 黑马, 23, 23.5, false, 中]
[Java, 赵敏, Java, MySQL, 黑马, 23, 23.5, false, 中]

2.2.3、ArrayList 类对于泛型的支持

import java.util.ArrayList;

/**
      目标: 能够使用泛型约束ArrayList集合操作的数据类型
 */
public class ArrayListDemo2 {
    public static void main(String[] args) {
        // ArrayList<String> list = new ArrayList<String>();
        ArrayList<String> list = new ArrayList<>(); // JDK 1.7开始,泛型后面的类型申明可以不写
        list.add("Java");
        list.add("MySQL");
        // list.add(23);
        // list.add(23.5);

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(23);
        list2.add(100);
        // list2.add("Java");


    }
}

泛型:

Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

2.2.4、ArrayList 类常用方法

在这里插入图片描述

代码演示

import java.util.ArrayList;

/**
    目标:掌握ArrayList集合的常用API
 */
public class ArrayListDemo3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Java");
        list.add("MySQL");
        list.add("MyBatis");
        list.add("HTML");

        // 1、public E get(int index):获取某个索引位置处的元素值
        String e = list.get(3);
        System.out.println(e);

        // 2、public int size():获取集合的大小(元素个数)
        System.out.println(list.size());

        // 3、完成集合的遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        // 4、public E remove(int index):删除某个索引位置处的元素值,并返回被删除的元素值
        System.out.println(list); // [Java, Java, MySQL, MyBatis, HTML]
        String e2 = list.remove(2);
        System.out.println(e2);
        System.out.println(list);

        // 5、public boolean remove(Object o):直接删除元素值,删除成功返回true,删除失败返回false
        System.out.println(list.remove("MyBatis"));
        System.out.println(list);

        ArrayList<String> list1 = new ArrayList<>();
        list1.add("Java");
        list1.add("C#");
        list1.add("Java");
        list1.add("MySQL");
        System.out.println(list1);
        // 只会删除第一次出现的这个元素值,后面的不删除
        System.out.println(list1.remove("Java"));
        System.out.println(list1);


        // 6、public E set(int index,E element):修改某个索引位置处的元素值。
        String e3 = list1.set(0 , "C++");
        System.out.println(e3);
        System.out.println(list1);


    }
}

输出结果:

MyBatis
5
Java
Java
MySQL
MyBatis
HTML
[Java, Java, MySQL, MyBatis, HTML]
MySQL
[Java, Java, MyBatis, HTML]
true
[Java, Java, HTML]
[Java, C#, Java, MySQL]
true
[C#, Java, MySQL]
C#
[C++, Java, MySQL]

2.2.5、ArrayList 类使用案例

遍历并删除元素值(从后往前扫描)

import java.util.ArrayList;

/**
   案例:从集合中遍历元素且删除。
 */
public class ArrayListTest4 {
    public static void main(String[] args) {
        // 1、创建集合对象:存入学生成绩(98,77,66,89,79,50,100)
        ArrayList<Integer> scores = new ArrayList<>();
        scores.add(98);
        scores.add(77);
        scores.add(66);
        scores.add(89);
        scores.add(79);
        scores.add(50);
        scores.add(100);
        System.out.println(scores);
        // [98, 77, 66, 89, 79, 50, 100]
        // [98, 66, 89, 50, 100]
        //                   i

        // 1、遍历集合中的每个元素
//        for (int i = 0; i < scores.size(); i++) {
//            int score = scores.get(i);
//            // 2、判断这个分数是否低于80分,如果低于则从集合中删除它
//            if(score < 80){
//                scores.remove(i); // 此处删完应该执行 i--
//            }
//        }
//        System.out.println(scores);


        // [98, 77, 66, 89, 79, 50, 100]
        // [98,  89, 100]
        //   i

        for (int i = scores.size() - 1; i >= 0 ; i--) {
            int score = scores.get(i);
            // 2、判断这个分数是否低于80分,如果低于则从集合中删除它
            if(score < 80){
                scores.remove(i);
            }
        }
        System.out.println(scores);
    }
}

输出结果:

[98, 77, 66, 89, 79, 50, 100]
[98, 89, 100]

注意:应该从后往前扫描,如果要从前往后扫描,在移除元素后应该执行 “i–”

存储自定义类型的对象 (结合数组)

  • 集合

在这里插入图片描述
直接输出集合是一串内存地址

System.out.println(movies);
// [com.itheima.arraylist.Movie@1b6d3586, com.itheima.arraylist.Movie@4554617c, com.itheima.arraylist.Movie@74a14482]
  • 数组
    在这里插入图片描述

学生信息系统的数据搜索

public class Student {
    private String studyId;
    private String name;
    private int age;
    private String className;

    public Student() {
    }

    public Student(String studyId, String name, int age, String className) {
        this.studyId = studyId;
        this.name = name;
        this.age = age;
        this.className = className;
    }

    public String getStudyId() {
        return studyId;
    }

    public void setStudyId(String studyId) {
        this.studyId = studyId;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}
import java.util.ArrayList;
import java.util.Scanner;

/**
    案例:学生信息系统:展示数据,并按照学号完成搜索
     学生类信息(学号,姓名,性别,班级)
     测试数据:
     "20180302","叶孤城",23,"护理一班"
     "20180303","东方不败",23,"推拿二班"
     "20180304","西门吹雪",26,"中药学四班"
     "20180305","梅超风",26,"神经科2班"
 */
public class ArrayListTest6 {
    public static void main(String[] args) {
        // 1、定义一个学生类,后期用于创建对象封装学生数据
        // 2、定义一个集合对象用于装学生对象
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student("20180302","叶孤城",23,"护理一班"));
        students.add(new Student("20180303","东方不败",23,"推拿二班"));
        students.add(new Student( "20180304","西门吹雪",26,"中药学四班"));
        students.add(new Student( "20180305","梅超风",26,"神经科2班"));
        System.out.println("学号\t\t名称\t年龄\t\t班级");

        // 3、遍历集合中的每个学生对象并展示其数据
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            System.out.println(s.getStudyId() +"\t\t" + s.getName()+"\t\t"
                    + s.getAge() +"\t\t" + s.getClassName());
        }

        // 4、让用户不断的输入学号,可以搜索出该学生对象信息并展示出来(独立成方法)
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请您输入要查询的学生的学号:");
            String id = sc.next();
            Student s = getStudentByStudyId(students, id);
            // 判断学号是否存在
            if(s == null){
                System.out.println("查无此人!");
            }else {
                // 找到了该学生对象了,信息如下
                System.out.println(s.getStudyId() +"\t\t" + s.getName()+"\t\t"
                        + s.getAge() +"\t\t" + s.getClassName());
            }
        }
    }

    /**
      根据学号,去集合中找出学生对象并返回。
     * @param students
     * @param studyId
     * @return
     */
    public static Student getStudentByStudyId(ArrayList<Student> students, String studyId){
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            if(s.getStudyId().equals(studyId)){
                return s;
            }
        }
        return null; // 查无此学号!
    }
}

输出结果:

学号		名称	年龄		班级
20180302		叶孤城		23		护理一班
20180303		东方不败		23		推拿二班
20180304		西门吹雪		26		中药学四班
20180305		梅超风		26		神经科2班
请您输入要查询的学生的学号:
20180302
20180302		叶孤城		23		护理一班
请您输入要查询的学生的学号:
20180307
查无此人!
请您输入要查询的学生的学号:

3、Java 项目:ATM系统

此项目可以深刻理解面向对象、参数传递、定义方法的好处

定义账户类

/**
    系统的账户类,代表账户的信息
 */
public class Account {
    private String cardId; // 卡号
    private String userName; // 用户名称
    private String passWord; // 密码
    private double money; // 账户余额
    private double quotaMoney; // 每次取现额度限度。

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public double getQuotaMoney() {
        return quotaMoney;
    }

    public void setQuotaMoney(double quotaMoney) {
        this.quotaMoney = quotaMoney;
    }
}

ATM 系统基本业务实现

import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

public class ATMSystem {
    public static void main(String[] args) {
        // 2、创建一个集合对象,用于后期存入账户对象。
        ArrayList<Account> accounts = new ArrayList<>();

        // 3、展示系统欢迎页面
        System.out.println("======欢迎您进入到黑马ATM系统===============");
        while (true) {
            System.out.println("1、登录账户");
            System.out.println("2、注册账户");
            System.out.println("请您选择操作:");
            Scanner sc = new Scanner(System.in);
            int command = sc.nextInt();
            switch (command){
                case 1:
                    // 登录操作
                    login(accounts, sc);
                    break;
                case 2:
                    // 注册账户
                    register(accounts, sc);
                    break;
                default:
                    System.out.println("当前输入的操作不存在!");
            }
        }
    }

    /**
       用户登录功能
     */
    private static void login(ArrayList<Account> accounts, Scanner sc) {
        System.out.println("==================欢迎您进入到登录操作======================");
        if(accounts.size() > 0){
            while (true) {
                System.out.println("请您输入登录的卡号:");
                String cardId = sc.next();
                // 2、根据卡号去集合中查询是否存在账户对象
                Account acc = getAccountByCardId(cardId , accounts);
                // 判断账户对象是否存在,存在说明卡号正确
                if(acc != null){
                    while (true) {
                        // 3、判断密码是否正确
                        System.out.println("请您输入登录的密码:");
                        String passWord = sc.next();
                        if(acc.getPassWord().equals(passWord)){
                            // 登录成功了!
                            System.out.println("欢迎你:" + acc.getUserName() + "先生/女士进入系统,您可开始办理你的业务了!");
                            // 展示登录成功后的操作界面。
                            showCommand(sc, acc, accounts);
                            return;
                        }else {
                            System.out.println("您的密码不正确!");
                        }
                    }
                }else {
                    System.out.println("卡号不存在,请确认!");
                }
            }
        }else {
            System.out.println("当前系统无任何账户,请先注册再登录!");
        }
    }

    /**
      登录后展示的界面
     */
    private static void showCommand(Scanner sc, Account acc, ArrayList<Account> accounts) {
        while (true) {
            System.out.println("==================欢迎您进入到操作界面======================");
            System.out.println("1、查询");
            System.out.println("2、存款");
            System.out.println("3、取款");
            System.out.println("4、转账");
            System.out.println("5、修改密码");
            System.out.println("6、退出");
            System.out.println("7、注销账户");
            System.out.println("请您输入操作命令:");
            int command = sc.nextInt();
            switch (command) {
                case 1:
                    // 查询账户信息展示
                    showAccount(acc);
                    break;
                case 2:
                    // 存款
                    depositMoney(acc,sc);
                    break;
                case 3:
                    // 取款
                    drawMoney(acc,sc);
                    break;
                case 4:
                    // 转账
                    transferMoney(acc, accounts, sc);
                    break;
                case 5:
                    // 修改密码
                    updatePassWord(acc, sc);
                    return; // 跳出当前操作的方法,最终回到首页
                case 6:
                    // 退出 回到首页
                    System.out.println("欢迎下次继续光临!!");
                    return; // 结束登录后的全部操作
                case 7:
                    // 注销账户
                    accounts.remove(acc); // 从集合对象中删除当前账户对象。
                    System.out.println("您的账户已经完成了销毁,您将不可以进行登录了!");
                    return;
                default:
                    System.out.println("您的操作命令有误!");
            }
        }

    }


    /**
      修改当前账户对象的密码
     * @param acc
     */
    private static void updatePassWord(Account acc, Scanner sc) {
        // 1、判断旧密码是否正确
        while (true) {
            System.out.println("请您输入当前密码认证:");
            String passWord = sc.next();
            if(acc.getPassWord().equals(passWord)){
                while (true) {
                    // 2、输入新密码
                    System.out.println("请您输入新密码:");
                    String newPassWord = sc.next();
                    System.out.println("请您确认新密码:");
                    String okPassWord = sc.next();
                    // 3、比对两次密码是否一致
                    if(newPassWord.equals(okPassWord)){
                        acc.setPassWord(okPassWord);
                        System.out.println("密码已经修改成功,请重新登录!");
                        return;
                    }else {
                        System.out.println("两次密码不一致!");
                    }
                }
            }else {
                System.out.println("您输入的密码有误。请重新确认密码!");
            }
        }
    }

    /**
     * 从当前账户对象中把金额转给其他账户对象。
     * @param acc
     * @param accounts
     * @param sc
     */
    private static void transferMoney(Account acc, ArrayList<Account> accounts, Scanner sc) {
        // 1、判断自己的账户中是否有钱
        if(acc.getMoney() <= 0){
            System.out.println("您自己都没钱,就别转了吧!");
            return;
        }

        // 2、判断总账户数量是否大于等于2个。
        if(accounts.size() >= 2){
            while (true) {
                // 3、让当前用户输入对方的账号进行转账
                System.out.println("请您输入对方卡号:");
                String cardId = sc.next();

                // 4、根据卡号查询出集合中的账户对象
                Account otherAcc = getAccountByCardId(cardId , accounts);
                // 5、判断账户对象是否存在,而且这个账户对象不能是自己。
                if(otherAcc != null){
                    // 6、判断当前账户是否是自己。
                    if(acc.getCardId().equals(otherAcc.getCardId())){
                        System.out.println("不能给自己账户转账!");
                    }else {
                        // 7、正式进入到转账逻辑了
                        // 黑马刘德华
                        String rs = "*" + otherAcc.getUserName().substring(1);
                        System.out.println("请您确认["+rs +"]的姓氏来确认!");
                        System.out.println("请您输入对方的姓氏:");
                        String preName = sc.next();
                        if(otherAcc.getUserName().startsWith(preName)){
                            // 认证通过
                            while (true) {
                                System.out.println("请您输入转账的金额(您最多可以转账:" + acc.getMoney() +"元):");
                                double money = sc.nextDouble();
                                if(money > acc.getMoney()){
                                    System.out.println("你不听话,没有这么多钱可以转!");
                                }else {
                                    // 开始转
                                    acc.setMoney(acc.getMoney() - money); // 更新自己账户
                                    otherAcc.setMoney(otherAcc.getMoney() + money);
                                    System.out.println("您已经完成转账!您当前还剩余:" + acc.getMoney());
                                    return;
                                }
                            }

                        }else {
                            System.out.println("您输入对方的信息有误!");
                        }
                    }
                }else {
                    System.out.println("您输入的转账卡号不存在!");
                }
            }
        }else {
            System.out.println("当前系统中没有其他账户可以转账,去注册一个账户吧!");
        }

    }

    private static void drawMoney(Account acc, Scanner sc) {
        System.out.println("==================欢迎进入账户取款操作======================");
        // 1、判断账户的余额是否高于等于100
        double money = acc.getMoney();
        if(money >= 100) {
            while (true) {
                // 2、输入取钱的金额
                System.out.println("请您输入取钱的金额:");
                double drawMoney = sc.nextDouble();
                // 3、判断取钱金额是否超过了当次限额
                if(drawMoney > acc.getQuotaMoney()){
                    System.out.println("您当前取款金额超过了每次限额!");
                }else {
                    // 4、判断当前取钱金额是超过了账户的余额
                    if(drawMoney > money){
                        System.out.println("当前余额不足!当前余额是:" + money);
                    }else {
                        // 更新账户余额
                        acc.setMoney(money - drawMoney);
                        System.out.println("您当前取钱完成,请拿走你的钱,当前剩余余额是:" + acc.getMoney());
                        break;
                    }
                }
            }
        }else {
            System.out.println("您当前账户余额不足100元,存钱去吧!");
        }
    }

    private static void depositMoney(Account acc, Scanner sc) {
        System.out.println("==================欢迎进入账户存款操作======================");
        System.out.println("请您输入存款金额:");
        double money = sc.nextDouble();
        acc.setMoney(acc.getMoney() + money);
        showAccount(acc);
    }

    private static void showAccount(Account acc) {
        System.out.println("==================您当前账户详情信息如下======================");
        System.out.println("卡号:" + acc.getCardId());
        System.out.println("户主:" + acc.getUserName());
        System.out.println("余额:" + acc.getMoney());
        System.out.println("当次取现额度:" + acc.getQuotaMoney());
    }

    /**
        开户功能
     */
    private static void register(ArrayList<Account> accounts, Scanner sc) {
        System.out.println("==================欢迎您进入到开户操作======================");
        // 2、创建一个账户对象封装账户信息
        Account acc = new Account();
        // 1、录入用户账户信息
        System.out.println("请您输入账户名称:");
        String userName =sc.next();
        acc.setUserName(userName);

        while (true) {
            System.out.println("请您输入账户密码:");
            String passWord =sc.next();
            System.out.println("请您输入确认密码:");
            String okPassWord =sc.next();
            if(okPassWord.equals(passWord)){
                // 密码无问题
                acc.setPassWord(okPassWord);
                break;
            }else {
                System.out.println("两次输入的密码不一致!");
            }
        }
        System.out.println("请您设置当次取现额度:");
        double quataMoney = sc.nextDouble();
        acc.setQuotaMoney(quataMoney);
        // 关键点:为当前账户生成一个随机的8位数字作为卡号,卡号不能与其他用户的卡号重复。
        String cardId = createCardId(accounts);
        acc.setCardId(cardId);

        // 3、把账户对象存入到集合容器对象中去
        accounts.add(acc);
        System.out.println("恭喜您,"+acc.getUserName()+"先生/女士,您开户完成,您的卡号是:" + acc.getCardId());
    }

    public static String createCardId(ArrayList<Account> accounts){
        while (true) {
            String cardId = "";
            // 随机8个数字
            Random r = new Random();
            for (int i = 1; i <= 8 ; i++) {
                cardId += r.nextInt(10);
            }
            // 判断这个卡号是否重复:根据卡号去查询账户对象
            Account account = getAccountByCardId(cardId , accounts);
            if(account == null){
                return cardId;
            }
        }
    }

    public static Account getAccountByCardId(String cardId , ArrayList<Account> accounts){
        for (int i = 0; i < accounts.size(); i++) {
            Account acc = accounts.get(i);
            if(acc.getCardId().equals(cardId)){
                return acc;
            }
        }
        return null;
    }
}

4、static 关键字

4.1、 static 的作用、修饰成员变量的用法

static关键字的作用

  • static 是静态的意思,可以修饰成员变量和成员方法。

  • static 修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。

  • 非 static 修饰成员变量是属于对象的,需要创建对象才能使用

成员变量

在这里插入图片描述

代码演示

public class User {
    // 在线人数信息:静态成员变量
    public static int onLineNumber = 161;
    // 实例成员变量
    private String name;
    private int age;

    public static void main(String[] args) {
        // 1、类名.静态成员变量
        User.onLineNumber++;
        // 注意:同一个类中访问静态成员变量,类名可以省略不写
        System.out.println(onLineNumber);

        // 2、对象.实例成员变量
        // System.out.println(name);
        User u1 = new User();
        u1.name = "猪八戒";
        u1.age = 36;
        System.out.println(u1.name);
        System.out.println(u1.age);
        // 对象.静态成员变量(不推荐这样访问)
        u1.onLineNumber++;

        User u2 = new User();
        u2.name = "孙悟空";
        u2.age = 38;
        System.out.println(u2.name);
        System.out.println(u2.age);
        // 对象.静态成员变量(不推荐这样访问)
        u2.onLineNumber++;

        System.out.println(onLineNumber);

    }
}

输出结果:

162
猪八戒
36
孙悟空
38
164

内存原理:

在这里插入图片描述

总结

  1. 成员变量的分类和访问分别是什么样的?
  • 静态成员变量(有static修饰,属于类、加载一次,可以被共享访问)

  • 实例成员变量(无static修饰,属于对象):

  1. 两种成员变量各自在什么情况下定义?
  • 静态成员变量:表示在线人数等需要被共享的信息。

  • 实例成员变量:属于每个对象,且每个对象信息不同时(name,age,…等)

4.2、static修饰成员方法的基本用法

成员方法的分类

  • 静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问。

  • 实例成员方法(无static修饰,属于对象),只能用对象触发访问。

使用场景

  • 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。

  • 如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。

代码演示

public class Student {
    private String name;
    private int age;

    /**
        实例方法:无static修饰,属于对象的,通常表示对象自己的行为,可以访问对象的成员变量
     */
    public void study(){
        System.out.println(name + "在好好学习,天天向上~~");
    }

    /**
        静态方法:有static修饰,属于类,可以被类和对象共享访问。
     */
    public static void getMax(int a, int b){
        System.out.println(a > b ? a : b);
    }

    public static void main(String[] args) {
        // 1、类名.静态方法
        Student.getMax(10, 100);
        // 注意:同一个类中访问静态成员 可以省略类名不写
        getMax(200, 20);

        // 2、对象.实例方法
        // study(); // 报错的
        Student s = new Student();
        s.name = "全蛋儿";
        s.study();

        // 3、对象.静态方法(不推荐)
        s.getMax(300,20);
    }
}

输出结果:

100
200
全蛋儿在好好学习,天天向上~~
300

内存原理

在这里插入图片描述

4.3、工具类

  • 工具类中定义的都是一些静态方法,每个方法都是以完成一个共用的功能为目的
  • 工具类的好处:一是调用方便,二是提高了代码复用(一次编写,处处可用)

验证码工具类

public class VerifyTool {
    /**
       私有构造器
     */
    private VerifyTool(){
    }

    /**
      静态方法
     */
    public static String createCode(int n){
        // 1、使用String开发一个验证码
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        // 2、定义一个变量用于存储5位随机的字符作为验证码
        String code = "";
        // 3、循环
        Random r = new Random();
        for (int i = 0; i < n; i++) {
            int index = r.nextInt(chars.length());
            // 4、对应索引提取字符
            code += chars.charAt(index);
        }
        return code;
    }
}
public class Login {
    public static void main(String[] args) {
        // 验证码:
        System.out.println("验证码:" + VerifyTool.createCode(10));
    }
}
public class Register {
    public static void main(String[] args) {
        // 验证码:
        System.out.println("验证码:" + VerifyTool.createCode(5));
    }
}

数组工具类

public class ArrayUtils {
    /**
       把它的构造器私有化
     */
    private ArrayUtils(){
    }

    /**
       静态方法,工具方法
     */
    public static String toString(int[] arr){
        if(arr != null ){
            String result = "[";
            for (int i = 0; i < arr.length; i++) {
                result += (i == arr.length - 1 ? arr[i] : arr[i] + ", ");
            }
            result += "]";
            return result;
        }else {
            return null;
        }
    }

    /**
     静态方法,工具方法
     */
    public static double getAverage(int[] arr){
        // 总和  最大值 最小值
        int max = arr[0];
        int min = arr[0];
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] > max){
                max = arr[i];
            }
            if(arr[i] < min){
                min = arr[i];
            }
            sum += arr[i];
        }
        return (sum - max - min)*1.0 / (arr.length - 2);
    }
}

测试

public class Test2 {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30};
        System.out.println(arr);
        System.out.println(ArrayUtils.toString(arr));
        System.out.println(ArrayUtils.getAverage(arr));

        int[] arr1 = null;
        System.out.println(ArrayUtils.toString(arr1));
        int[] arr2 = {};
        System.out.println(ArrayUtils.toString(arr2));

    }
}

输出结果:

[I@1b6d3586
[10, 20, 30]
20.0
null
[]

工具类的定义注意

  • 建议将工具类的构造器进行私有,工具类无需创建对象。

  • 里面都是静态方法,直接用类名访问即可。

4.4、代码块

代码块概述

  • 代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。

  • 在Java类下,使用 { } 括起来的代码被称为代码块

代码块分类

  1. 静态代码块:
  • 格式:static{}

  • 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次

  • 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。

  1. 构造代码块了解,用得少):
  • 格式:{}

  • 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行

  • 使用场景:初始化实例资源。

静态代码块作用

  • 如果要在启动系统时对数据进行初始化。

  • 建议使用静态代码块完成数据的初始化操作,代码优雅。

静态代码块优先执行

public class TestDemo1 {

    public static String schoolName;

    public static void main(String[] args) {
        // 目标:学习静态代码块的特点、基本作用
        System.out.println("=========main方法被执行输出===========");
        System.out.println(schoolName);
    }

    /**
     特点:与类一起加载,自动触发一次,优先执行
     作用:可以在程序加载时进行静态数据的初始化操作(准备内容)
     */
    static{
        System.out.println("==静态代码块被触发执行==");
        schoolName = "黑马程序员";
    }
}

输出结果:

==静态代码块被触发执行==
=========main方法被执行输出===========
黑马程序员

静态代码块应用

import java.util.ArrayList;

public class StaticCodeTest3 {
    /**
       模拟初始化牌操作
         点数: "3","4","5","6","7","8","9","10","J","Q","K","A","2"
         花色: "♠", "♥", "♣", "♦"
       1、准备一个容器,存储54张牌对象,这个容器建议使用静态的集合。静态的集合只加载一次。
     */
    // int age = 12;
    public static ArrayList<String> cards = new ArrayList<>();

    /**
       2、在游戏启动之前需要准备好54张牌放进去,使用静态代码块进行初始化
     */
    static{
        // 3、加载54张牌进去。
        // 4、准备4种花色:类型确定,个数确定了
        String[] colors = {"♠", "♥", "♣", "♦"};
        // 5、定义点数
        String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        // 6、先遍历点数、再组合花色
        for (int i = 0; i < sizes.length; i++) {
            // sizes[i]
            for (int j = 0; j < colors.length; j++) {
                cards.add(sizes[i] + colors[j]);
            }
        }
        // 7、添加大小王
        cards.add("小🃏");
        cards.add("大🃏");
    }

    public static void main(String[] args) {
        System.out.println("新牌:" +  cards);
    }
}

输出结果:

新牌:[3♠, 3♥, 3♣, 3♦, 4♠, 4♥, 4♣, 4♦, 5♠, 5♥, 5♣, 5♦, 6♠, 6♥, 6♣, 6♦, 7♠, 7♥, 7♣, 7♦, 8♠, 8♥, 8♣, 8♦, 9♠, 9♥, 9♣, 9♦, 10♠, 10♥, 10♣, 10♦, J♠, J♥, J♣, J♦, Q♠, Q♥, Q♣, Q♦, K♠, K♥, K♣, K♦, A♠, A♥, A♣, A♦, 2♠, 2♥, 2♣, 2♦, 小🃏, 大🃏]

构造代码块(实例代码块)

  • 构造器代码块优先于构造器先执行
public class TestDemo2 {

    private String name;

    /**
       属于对象的,与对象一起加载,自动触发执行。
     */
    {
        System.out.println("==构造代码块被触发执行一次==");
        name = "张麻子";
    }

    public TestDemo2(){
        System.out.println("==构造器被触发执行==");
    }

    public static void main(String[] args) {
        // 目标:学习构造代码块的特点、基本作用
        TestDemo2 t = new TestDemo2();
        System.out.println(t.name);

        TestDemo2 t1 = new TestDemo2();
        System.out.println(t1.name);
    }

}

输出结果

==构造代码块被触发执行一次==
==构造器被触发执行==
张麻子
==构造代码块被触发执行一次==
==构造器被触发执行==
张麻子

4.5、单例设计模式

饿汉式

/**
    目标:学会使用恶汉单例模式设计单例类
 */
public class SingleInstance1 {
    /**
       static修饰的成员变量,静态成员变量,加载一次,只有一份
     */
    // public static int onLineNumber = 21;
    public static SingleInstance1 instance = new SingleInstance1();

    /**
        1、必须私有构造器:私有构造器对外不能被访问。
     */
    private SingleInstance1(){
    }


}
public class Test {
    public static void main(String[] args) {
//        SingleInstance1 s1 = new SingleInstance1();
//        SingleInstance1 s2 = new SingleInstance1();
//        SingleInstance1 s3 = new SingleInstance1();

        SingleInstance1 s1 = SingleInstance1.instance;
        SingleInstance1 s2 = SingleInstance1.instance;
        SingleInstance1 s3 = SingleInstance1.instance;
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s1 == s2);
    }
}

输出结果:

com.itheima.d6_singleinstance.SingleInstance1@1b6d3586
com.itheima.d6_singleinstance.SingleInstance1@1b6d3586
com.itheima.d6_singleinstance.SingleInstance1@1b6d3586
true

懒汉式(线程不安全)

挖坑:后面会具体分析

/**
    目标:设计懒汉单例
 */
public class SingleInstance2 {
    /**
       2、定义一个静态的成员变量用于存储一个对象,一开始不要初始化对象,因为人家是懒汉
     */
    private static SingleInstance2 instance;

    /**
       1、私有构造器啊
     */
    private SingleInstance2(){
    }

    /**
      3、提供一个方法暴露,真正调用这个方法的时候才创建一个单例对象
     */
    public static SingleInstance2 getInstance(){
        if(instance == null){
            // 第一次来拿对象,为他做一个对象
            instance = new SingleInstance2();
        }
        return instance;
    }
}
public class Test2 {
    public static void main(String[] args) {
        // 得到一个对象
        SingleInstance2 s1 = SingleInstance2.getInstance();
        SingleInstance2 s2 = SingleInstance2.getInstance();

        System.out.println(s1 == s2);
    }
}

输出结果:

true

4.6、static 的注意事项:

  • 静态方法只能访问静态的成员,不可以直接访问实例成员。

  • 实例方法可以访问静态的成员,也可以访问实例成员。

  • 静态方法中是不可以出现this关键字的。

总结:总之,记住静态方法无需创建对象就能使用,一切就很好理解了。

5、继承

5.1、概述

什么是继承?

  • Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系。

  • Student称为子类(派生类),People称为父类(基类 或超类)。

使用继承的好处

  • 当子类继承父类后,就可以直接使用父类公共的属性和方法了。因此,用好这个技术可以很好的我们提高代码的复用性。

继承展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 子类继承父类,子类可以得到父类的属性和行为,子类可以使用。Java中子类更强大。

内存原理

在这里插入图片描述

继承的特点

  • 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
  • Java是单继承模式:一个类只能继承一个直接父类。
  • Java不支持多继承、但是支持多层继承。
  • Java中所有的类都是Object类的子类。

注意事项

1、子类是否可以继承父类的构造器?

  • 不可以的,子类有自己的构造器,父类构造器用于初始化父类对象。

2、子类是否可以继承父类的私有成员?

  • 可以的,只是不能直接访问。(挖坑,后面填)

3、子类是否可以继承父类的静态成员?

  • 有争议的知识点。
  • 子类可以直接使用父类的静态成员(共享)
  • 但个人认为:子类不能继承父类的静态成员。(共享并非继承)

4、在子类方法中访问成员(成员变量、成员方法)满足:就近原则

  • 先子类局部范围找

  • 然后子类成员范围找

  • 然后父类成员范围找,如果父类范围还没有找到则报错

补充:

成员变量:

1、成员变量定义在类中,在整个类中都可以被访问。

2、成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。

3、成员变量有默认初始化值。

局部变量:

1、局部变量只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。

2、局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。

3、局部变量没有默认初始化值

5.2、super 关键字

  • 可以通过super关键字,指定访问父类的成员。
public class ExtendsDemo {
    public static void main(String[] args) {
        Wolf w = new Wolf();
        System.out.println(w.name); // 子类的
        w.showName();
    }
}

class Animal{
    public String name = "父类动物";
}

class Wolf extends Animal{
    public String name = "子类动物";

    public void showName(){
        String name = "局部名称";
        System.out.println(name); // 局部的
        System.out.println(this.name); // 子类name
        System.out.println(super.name); // 父类name
    }
}

输出结果:

子类动物
局部名称
子类动物
父类动物

5.3、方法重写

什么是方法重写?

  • 在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。

方法重写的应用场景

  • 当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。

  • 子类可以重写父类中的方法。

案例演示

  • 旧手机的功能只能是基本的打电话,发信息

  • 新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片。

父类

public class Phone {
    public void call(){
        System.out.println("打电话开始~~~");
    }

    public void sendMessage(){
        System.out.println("发送短信开始~~~");
    }
}

子类

public class NewPhone extends Phone{
    /**
      方法重写了
     */
    @Override
    public void call() {
        super.call();
        System.out.println("支持视频通话~~~");
    }

    /**
     方法重写了
     */
    @Override
    public void sendMessage() {
        super.sendMessage();
        System.out.println("支持发送图片和视频~~~");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        NewPhone huawei = new NewPhone();
        huawei.call();
        huawei.sendMessage();
    }
}

输出结果:

打电话开始~~~
支持视频通话~~~
发送短信开始~~~
支持发送图片和视频~~~

@Override重写注解

  • @Override是放在重写后的方法上,作为重写是否正确的校验注解。

  • 加上该注解后如果重写错误,编译阶段会出现错误提示。

  • 建议重写方法都加@Override注解,代码安全,优雅!

方法重写注意事项和要求

  • 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。

  • 私有方法不能被重写。

  • 子类重写父类方法时,访问权限必须大于或者等于父类 (暂时了解 :缺省 < protected < public)

  • 子类不能重写父类的静态方法,如果重写会报错的

  • 开发中一般重写方法的名称、形参列表,访问权限和被重写方法保持一致,只是更改方法里面内容

5.4、子类构造器

5.4.1、子类构造器的特点

特点

子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。

为什么?

  • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。

  • 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。

怎么调用父类构造器的?

  • 子类构造器的第一行语句默认都是:super(),不写也存在。

代码演示

父类

public class Animal {
    public Animal(){
        System.out.println("==父类Animal无参数构造器被执行===");
    }
}

子类

public class Cat extends Animal{
    public Cat(){
//        super(); // 默认的,写不写都有,默认就是找父类无参数构造器
        System.out.println("==子类Cat无参数构造器被执行===");
    }

    public Cat(String n){
//        super(); // 默认的,写不写都有,默认就是找父类无参数构造器
        System.out.println("==子类Cat有参数构造器被执行===");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        Cat c = new Cat();
        System.out.println("------------");
        Cat c1 = new Cat("叮当猫");
    }
}

输出结果:

==父类Animal无参数构造器被执行===
==子类Cat无参数构造器被执行===
------------
==父类Animal无参数构造器被执行===
==子类Cat有参数构造器被执行===

5.4.1、子类构造器访问父类有参构造器

super调用父类有参数构造器的作用:

  • 初始化继承自父类的数据。

如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?

  • 会报错。因为子类默认是调用父类无参构造器的。

如何解决?

  • 子类构造器中可以通过书写 super(…),手动调用父类的有参数构造器

代码演示

父类

public class People {
    private String name;
    private int age;

    public People() {
    }

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

子类

public class Student extends People{
    private String className;

    public Student(){
    }

    public Student(String name, int age, String className) {
        super(name, age);
        this.className = className;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Student s = new Student("张三", 21, "99期");
        System.out.println(s.getName());
        System.out.println(s.getAge());
        System.out.println(s.getClassName());
    }
}

输出结果

张三
21
99期

5.5、this、super使用总结

在这里插入图片描述

this() 访问本类构造器案例分析

在这里插入图片描述

this(…)和super(…)使用注意点:

  • 子类通过 this (…)去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。

  • 注意:this(…) super(…) 都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。

6、包、常量修饰符、final

6.1、包

  • 包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护。

导包

  • 相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!导包格式:import 包名.类名;
  • 假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问。(面试可能会问到)

6.2、权限修饰符

什么是权限修饰符?

  • 权限修饰符:是用来控制一个成员能够被访问的范围的。
  • 可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。

权限修饰符的分类和具体作用范围

  • 权限修饰符:有四种作用范围由小到大(private -> 缺省 -> protected - > public )

在这里插入图片描述

自己定义成员(方法,成员变量,构造器等)一般满足如下要求

  • 成员变量一般私有。
  • 方法一般公开。
  • 如果该成员只希望本类访问,使用private修饰。
  • 如果该成员只希望本类,同一个包下的其他类和子类访问,使用protected修饰。

在子类中应该用子类对象,而不是用父类对象去访问父类对象中的 protected 方法

public class Fu {
    // 1.private 只能本类中访问
    private void show1() {
        System.out.println("private");
    }

    // 2.缺省:本类,同一个包下的类中。
    void show2() {
        System.out.println("缺省");
    }

    // 3.protected:本类,同一个包下的类中,其他包下的子类
    protected void show3() {
        System.out.println("protected");
    }

    // 4.任何地方都可以
    public void show4() {
        System.out.println("public");
    }

    public static void main(String[] args) {
        //创建Fu的对象,测试看有哪些方法可以使用
        Fu f = new Fu();
        f.show1();
        f.show2();
        f.show3();
        f.show4();
    }
}
public class Zi extends Fu {
    public static void main(String[] args) {
        //创建Zi的对象,测试看有哪些方法可以使用
        Zi z = new Zi();
        z.show3();
        z.show4();
    }
}

在这里插入图片描述

6.3、final

final的作用

  • final 关键字是最终的意思,可以修饰(方法,变量,类)

  • 修饰方法:表明该方法是最终方法,不能被重写。

  • 修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。

  • 修饰类:表明该类是最终类,不能被继承。

  • 工具类一般可以使用 Final 修饰

final修饰变量的注意

  • final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
  • final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的
public class Test2 {
    public static void main(String[] args) {
        // final修饰变量的注意事项:
        // 1、final修饰基本类型变量,其数据不能再改变
        final double rate = 3.14;
        // rate = 3.15; // 第二次赋值,报错

        // 2、final修饰引用数据类型的变量,变量中存储的地址不能被改变,但是地址指向的对象内容可以改变。
        final int[] arr = {10, 20, 30};
        System.out.println(arr);
        System.out.println(arr[1]);
        // arr = null; // 属于第二次赋值,arr中的地址不能被改变
        arr[1] = 200;
        System.out.println(arr);
        System.out.println(arr[1]);
    }
}
[I@1b6d3586
20
[I@1b6d3586
200

6.4、补充:局部变量和成员变量的区别

参考链接

https://www.cnblogs.com/cainiao-chuanqi/p/11073993.html

1.定义的位置不一样【重点】

  • 局部变量:在方法的内部
  • 成员变量:在方法的外部,直接写在类当中

2.作用范围不一样【重点】

  • 局部变量:只有方法当中才可以使用,出了方法就不能再用了
  • 成员变量:整个类都可以通用

3.默认值不一样【重点】

  • 局部变量:没有默认值,如果要想使用,必须手动进行赋值
  • 成员变量:如果没有赋值,会有默认值,规则和数组一样

4.内存的位置不一样(了解)

  • 局部变量:位于栈内存
  • 成员变量:位于堆内存

5.生命周期不一样(了解)

  • 局部变量:随着方法进栈而诞生,随着方法出栈而消失
  • 成员变量:随着对象的创建而诞生,随着对象被垃而消失

7、常量、枚举

7.1、常量

概述

  • 常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
  • 常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。
  • 常量命名规范:英文单词全部大写,多个单词下划线连接起来。

在这里插入图片描述

常量的执行原理

  • 在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量。
  • 这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。
    在这里插入图片描述

常量做信息标志和分类

import javax.swing.*;
import java.awt.event.ActionEvent;

/**
    目标: 常量的其他作用,做信息标志和信息分类(其实也是一种配置形式)
 */
public class ConstantDemo2 {
    public static final int UP = 1; // 上
    public static final int DOWN = 2; // 上
    public static final int LEFT = 3; // 左
    public static final int RIGHT = 4; // 右

    public static void main(String[] args) {
        // 1、创建一个窗口对象(桌子)
        JFrame win = new JFrame();
        // 2、创建一个面板对象(桌布)
        JPanel panel = new JPanel();
        // 3、把桌布垫在桌子上
        win.add(panel);
        // 4、创建四个按钮对象
        JButton btn1 = new JButton("上");
        JButton btn2 = new JButton("下");
        JButton btn3 = new JButton("左");
        JButton btn4 = new JButton("右");
        // 5、把按钮对象添加到桌布上去
        panel.add(btn1);
        panel.add(btn2);
        panel.add(btn3);
        panel.add(btn4);
        // 6、显示窗口
        win.setLocationRelativeTo(null);
        win.setSize(300,400);
        win.setVisible(true);


        btn1.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(UP) ; // 让玛丽往上跳
            }
        });
        btn2.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(ConstantDemo2.DOWN) ; // 让玛丽往下跳
            }
        });
        btn3.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(LEFT) ; // 让玛丽往左跑
            }
        });
        btn4.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(RIGHT) ; // 让玛丽往右跑
            }
        });
    }

    public static void move(int flag){
        // 控制玛丽移动
        switch (flag) {
            case UP:
                System.out.println("玛丽往上飞了一下~~");
                break;
            case DOWN:
                System.out.println("玛丽往下蹲一下~~");
                break;
            case LEFT:
                System.out.println("玛丽往左跑~~");
                break;
            case RIGHT:
                System.out.println("玛丽往→跑~~");
                break;
        }
    }
}

7.2、枚举

概述

  • 枚举是Java中的一种特殊类型
  • 枚举的作用:“是为了做信息的标志和信息的分类”。

反编译后观察枚举的特征

在这里插入图片描述

枚举改进超级玛丽代码

/**
   做信息标志和分类
 */
public enum Orientation {
    UP, DOWN, LEFT, RIGHT;
}
import javax.swing.*;
import java.awt.event.ActionEvent;

/**
    目标: 常量的其他作用,做信息标志和信息分类(其实也是一种配置形式)
 */
public class EnumDemo2 {
    public static void main(String[] args) {
        // 1、创建一个窗口对象(桌子)
        JFrame win = new JFrame();
        // 2、创建一个面板对象(桌布)
        JPanel panel = new JPanel();
        // 3、把桌布垫在桌子上
        win.add(panel);
        // 4、创建四个按钮对象
        JButton btn1 = new JButton("上");
        JButton btn2 = new JButton("下");
        JButton btn3 = new JButton("左");
        JButton btn4 = new JButton("右");
        // 5、把按钮对象添加到桌布上去
        panel.add(btn1);
        panel.add(btn2);
        panel.add(btn3);
        panel.add(btn4);
        // 6、显示窗口
        win.setLocationRelativeTo(null);
        win.setSize(300,400);
        win.setVisible(true);


        btn1.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(Orientation.UP) ; // 让玛丽往上跳
            }
        });
        btn2.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(Orientation.DOWN) ; // 让玛丽往下跳
            }
        });
        btn3.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(Orientation.LEFT) ; // 让玛丽往左跑
            }
        });
        btn4.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move(Orientation.RIGHT) ; // 让玛丽往右跑
            }
        });
    }

    public static void move(Orientation o){
        // 控制玛丽移动
        switch (o) {
            case UP:
                System.out.println("玛丽往上飞了一下~~");
                break;
            case DOWN:
                System.out.println("玛丽往下蹲一下~~");
                break;
            case LEFT:
                System.out.println("玛丽往左跑~~");
                break;
            case RIGHT:
                System.out.println("玛丽往→跑~~");
                break;
        }
    }
}

选择常量做信息标志和分类

  • 虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨。

枚举做信息标志和分类

  • 代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术!建议使用!

拓展:向枚举中添加新方法

public enum Color {  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  
    // 成员变量  
    private String name;  
    private int index;  
    // 构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    // 普通方法  
    public static String getName(int index) {  
        for (Color c : Color.values()) {  
            if (c.getIndex() == index) {  
                return c.name;  
            }  
        }  
        return null;  
    }  
    // get set 方法  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getIndex() {  
        return index;  
    }  
    public void setIndex(int index) {  
        this.index = index;  
    }  
}  

测试

public class TestColor {

    public static void main(String[] args) {
        System.out.println(Color.getName(1));
        System.out.println(Color.getName(2));
        System.out.println(Color.getName(3));
        System.out.println(Color.getName(4));

        Color.RED.setName("红红火火恍恍惚惚");
        System.out.println(Color.RED.getName());
    }

}

输出内容

红色
绿色
白色
黄色
红红火火恍恍惚惚

8、抽象类

8.1、概述

在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。

抽象的使用总结与注意事项

  • 抽象类可以理解成类的不完整设计图,是用来被子类继承的。
  • 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。

抽象类的作用是什么样的?

  • 可以被子类继承、充当模板的、同时也可以提高代码复用。

抽象方法是什么样的?

  • 只有方法签名,没有方法体,使用了abstract修饰。

继承抽象类有哪些要注意?

  • 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法。
  • 否则这个类也必须定义成抽象类。

8.2、抽象类的案例

抽象父类

/**
     抽象父类
 */
public abstract class Card {
    private String name; // 主人名称
    private double money;

    /**
      子类一定要支付的,但是每个子类支付的情况不一样,所以父类把支付定义成抽象方法,交给具体子类实现
     */
    public abstract void pay(double money);

    public String getName() {
        return name;
    }

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

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}

实现子类

/**
   金卡
 */
public class GoldCard extends Card{
    @Override
    public void pay(double money) {
        // 优惠后的金额算出来:
        double rs = money * 0.8;
        double lastMoney = getMoney() - rs;
        System.out.println(getName() + "当前账户总金额:"
                + getMoney() +",当前消费了:" + rs +",消费后余额剩余:" +
                lastMoney);

        setMoney(lastMoney); // 更新账户对象余额
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        GoldCard c = new GoldCard();
        c.setMoney(10000); // 父类的
        c.setName("三石");
        c.pay(300);
        System.out.println("余额:" + c.getMoney());
    }
}

输出结果

三石当前账户总金额:10000.0,当前消费了:240.0,消费后余额剩余:9760.0
余额:9760.0

8.3、抽象类的特征和注意事项

  • 抽象类得到了抽象方法,失去了创建对象的能力
  • 类有的成员(成员变量、方法、构造器)抽象类都具备
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
  • 不能用abstract修饰变量、代码块、构造器。

抽象类为什么不能创建对象?

如果抽象类可以创建对象,那么就可以调用里面的抽象方法,方法还没被实现就被调用,程序就崩了。

8.4、补充:final和abstract是什么关系?

互斥关系

  • abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
  • 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。

8.5、模板方法模式

使用场景

  • 当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。

模板方法模式实现步骤

  • 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
  • 模板方法中不能决定的功能定义成抽象方法让具体子类去实现。

案例:银行利息结算系统

需求:

  • 某软件公司要为某银行的业务支撑系统开发一个利息结算系统,账户有活期和定期账户两种,
  • 活期是0.35%,定期是 1.75%,定期如果满10万额外给予3%的收益。
  • 结算利息要先进行用户名、密码验证,验证失败直接提示,登录成功进行结算

使用模板方法模式前

/**
  活期账户
 */
public class CurrentAccount {
    private String cardId;
    private double money;

    public CurrentAccount() {
    }

    public CurrentAccount(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
     登录结算利息
     */
    public void handle(String loginName , String passWord ){
        // a.判断登录是否成功
        if("itheima".equals(loginName) && "123456".equals(passWord)){
            System.out.println("登录成功。。");
            // b.正式结算利息
            double result =  money * 0.0175; // 结算利息了
            // c.输出利息详情
            System.out.println("本账户利息是:"+ result);
        }else{
            System.out.println("用户名或者密码错误了");
        }
    }


    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}
/**
    定期账户
 */
public class FixedAccount {
    private String cardId;
    private double money;

    public FixedAccount() {
    }

    public FixedAccount(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
      登录结算
     */
    public void handle(String loginName , String passWord ){
        // a.判断登录是否成功
        if("itheima".equals(loginName) && "123456".equals(passWord)){
            System.out.println("登录成功。。");
            // b.正式结算利息
            double result =  money * 0.035; // 结算利息了
            if(money >= 100000){
                result += money * 0.03;
            }
            // c.输出利息详情
            System.out.println("本账户利息是:"+ result);
        }else{
            System.out.println("用户名或者密码错误了");
        }
    }


    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}

缺点:代码冗余,有相当多的实现方法

改进策略分析

  1. 创建一个抽象的账户类Account作为父类模板,提供属性(卡号,余额)
  2. 在父类Account中提供一个模板方法实现登录验证,利息结算、利息输出。
  3. 具体的利息结算定义成抽象方法,交给子类实现。
  4. 定义活期账户类,让子类重写实现具体的结算方法
  5. 定义定期账户类,让子类重写实现具体的结算方法
  6. 创建账户对象,完成相关功能。

使用模板方法模式后

public abstract class Account {
    private String cardId;
    private double money;

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
      模板方法
     */
    public final void handle(String loginName , String passWord ){
        // a.判断登录是否成功
        if("itheima".equals(loginName) && "123456".equals(passWord)){
            System.out.println("登录成功。。");
            // b.正式结算利息
            // 当前模板方法知道所有子类账户都要结算利息,但是具体怎么结算,模板不清楚,交给具体的子类来计算
            double result = calc();
            // c.输出利息详情
            System.out.println("本账户利息是:"+ result);
        }else{
            System.out.println("用户名或者密码错误了");
        }
    }

    public abstract double calc();


    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}
/**
  活期账户
 */
public class CurrentAccount extends Account {

    public CurrentAccount(String cardId,  double money) {
        super(cardId, money);
    }

    @Override
    public double calc() {
        // b.正式结算利息
        double result =  getMoney() * 0.0175; // 结算利息了
        return result;
    }

}

板方法我们是建议使用final修饰的,这样会更专业,那么为什么呢?

模板方法是给子类直接使用的,不是让子类重写的,一旦子类重写了模板方法就失效了。

模板方法模式解决了什么问题?

  1. 极大的提高了代码的复用性
  2. 模板方法已经定义了通用结构,模板不能确定的定义成抽象方法。
  3. 使用者只需要关心自己需要实现的功能即可。

9、接口

9.1、接口的默认行为

/**
   接口
 */
public interface SportManInterface {
    // 接口中的成员:JDK 1.8之前只有常量 和 抽象方法
    // public static final 可以省略不写,接口默认会为你加上!
    // public static final String SCHOOL_NAME = "黑马";
    String SCHOOL_NAME = "黑马";

    // 2、抽象方法
    //  public abstract 可以省略不写,接口默认会为你加上!
    // public abstract void run();
    void run();

    // public abstract void eat();
    void eat();
}

9.2、接口的基本使用:被实现

接口的用法

接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类。

public class PingPongMan implements SportMan , Law{
    @Override
    ...
}

从上面可以看出,接口可以被类单实现,也可以被类多实现。

接口实现的注意事项

一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。

9.3、接口与接口的关系:多继承

基本小结

  • 类和类的关系:单继承。
  • 类和接口的关系:多实现。
  • 接口和接口的关系:多继承,一个接口可以同时继承多个接口。

接口多继承的作用

  • 规范合并,整合多个接口为同一个接口,便于子类实现。

接口1

public interface Law {
    void rule(); // 遵章守法
    void eat();
}

接口2

public interface People {
    void eat();
}

接口3

public interface SportMan extends Law, People {
    void run();
    void competition();
}

实现类

/**
   实现类
 */
// public class BasketballMan implements Law, SportMan, People {
public class BasketballMan implements SportMan{

    @Override
    public void rule() {

    }

    @Override
    public void eat() {

    }


    @Override
    public void run() {

    }

    @Override
    public void competition() {

    }
}

9.4、JDK8开始接口新增方法(了解即可)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9.5、接口的注意事项(了解即可)

1、接口不能创建对象
2、一个类实现多个接口,多个接口中有同样的静态方法不冲突。
3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
5、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。

多态

方法调用:编译看左边,运行看右边。
变量调用:编译看左边,运行也看左边。(多态侧重行为多态)

父类

/**
    父类
 */
public class Animal {
    public String name = "动物名称";
    public void run(){
        System.out.println("动物可以跑~~");
    }
}

子类1

public class Dog extends Animal{
    public String name = "狗名称";
    @Override
    public void run() {
        System.out.println("🐕跑的贼溜~~~~~");
    }
}

子类2

public class Tortoise extends Animal{
    public String name = "乌龟名称";

    @Override
    public void run() {
        System.out.println("🐢跑的非常慢~~~");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        // 目标:先认识多态的形式
        // 父类  对象名称 = new 子类构造器();
        Animal a = new Dog();
        a.run(); // 方法调用:编译看左,运行看右
        System.out.println(a.name); // 方法调用:编译看左,运行也看左,动物名称

        Animal a1 = new Tortoise();
        a1.run();
        System.out.println(a1.name); // 动物名称
    }
}

输出结果

🐕跑的贼溜~~~~~
动物名称
🐢跑的非常慢~~~
动物名称

10、多态

10.1、多态的概述

什么是多态?

  • 同类型的对象,执行同一个行为,会表现出不同的行为特征。

多态的常见形式

父类类型 对象名称 = new 子类构造器;
接口     对象名称 = new 实现类构造器;

多态中成员访问特点

  • 方法调用:编译看左边,运行看右边。
  • 变量调用:编译看左边,运行也看左边。(多态侧重行为多态)

多态的前提

  • 有继承/实现关系;有父类引用指向子类对象;有方法重写。

10.2、多态的优势

优势

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。

    Animal a = new Dog();
    a.run(); // 后续业务行为随对象而变,后续代码无需修改
    
  • 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。

多态下不能使用子类的独有功能
在这里插入图片描述
可用强制类型转换解决,下文介绍。

10.3、多态下引用数据类型的类型转换

自动类型转换(从子到父):

  • 子类对象赋值给父类类型的变量指向。

强制类型转换(从父到子)

  • 此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
  • 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。

多态下引用数据类型的类型转换

Animal a = new Dog();
a.run();
//a.lookDoor(); // 多态下无法调用子类独有功能

// 强制类型转换:可以实现调用子类独有功能的
Dog d = (Dog) a;
d.lookDoor();

如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException

Animal t = new Tortoise();
Dog d = (Dog)t; // 出现异常 ClassCastException

Java建议强转转换前使用 instanceof 判断当前对象的真实类型,再进行强制转换

  • 变量名 instanceof 真实类型
  • 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之返回false。
// 建议强制转换前,先判断变量指向对象的真实类型,再强制类型转换。
if(a instanceof Tortoise){
    Tortoise t = (Tortoise) a;
    t.layEggs();
}else if(a instanceof Dog){
    Dog d1 = (Dog) a;
    d1.lookDoor();
}

案例

需求:

  • 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
  • 鼠标:被安装时可以完成接入、调用点击功能、拔出功能。
  • 键盘:被安装时可以完成接入、调用打字功能、拔出功能。

分析:

  • 定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。
  • 提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。
  • 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。

usb接口

public interface USB {
    void connect();
    void unconnect();
}

鼠标类

/**
   实现类(子类)
 */
public class Mouse implements USB{
    private String name;

    public Mouse(String name) {
        this.name = name;
    }

    @Override
    public void connect() {
        System.out.println(name + "成功的接入了设备了~~~");
    }

    @Override
    public void unconnect() {
        System.out.println(name + "成功的从设备弹出了~~~");
    }

    /**
      独有功能
     */
    public void click(){
        System.out.println(name + "双击点亮小红心~~~~");
    }

    public String getName() {
        return name;
    }

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

键盘类

/**
   实现类(子类)
 */
public class KeyBoard implements USB{
    private String name;

    public KeyBoard(String name) {
        this.name = name;
    }

    @Override
    public void connect() {
        System.out.println(name + "成功的接入了设备了~~~");
    }

    @Override
    public void unconnect() {
        System.out.println(name + "成功的从设备弹出了~~~");
    }

    /**
      独有功能
     */
    public void keyDown(){
        System.out.println(name + "写下了:老铁,6666,下次再来哦,老弟~~~~");
    }

    public String getName() {
        return name;
    }

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

电脑类

public class Computer {
    /**
       提供一个安装的入口:行为。
     */
    public void installUSB(USB u){
        u.connect();

        // 独有功能
        if(u instanceof Mouse){
            Mouse m = (Mouse) u;
            m.click();
        }else if(u instanceof KeyBoard) {
            KeyBoard k = (KeyBoard) u;
            k.keyDown();
        }

        u.unconnect();
    }
}

测试类

/**
    目标:USB设备模拟
    1、定义USB接口:接入 拔出
    2、定义2个USB的实现类:鼠标、键盘。
    3、创建一个电脑对象,创建USB设备对象,安装启动。
 */
public class Test {
    public static void main(String[] args) {
        // a、创建电脑对象
        Computer c = new Computer();
        // b、创建USB设备对象
        USB u = new Mouse("罗技鼠标");
        c.installUSB(u);

        USB k = new KeyBoard("双飞燕键盘");
        c.installUSB(k);
    }

}

输出结果

罗技鼠标成功的接入了设备了~~~
罗技鼠标双击点亮小红心~~~~
罗技鼠标成功的从设备弹出了~~~
双飞燕键盘成功的接入了设备了~~~
双飞燕键盘写下了:老铁,6666,下次再来哦,老弟~~~~
双飞燕键盘成功的从设备弹出了~~~

11、内部类

11.1、概述

内部类

  • 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。
public class People{
	// 内部类    
	public class Heart{
    }
}

内部类的使用场景、作用

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
  • 内部类通常可以方便访问外部类的成员,包括私有的成员。
  • 内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制。

11.2、静态内部类[了解]

什么是静态内部类?

  • 有static修饰,属于外部类本身。
  • 它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer{
    // 静态成员内部类
    public static class Inner{
    }
}

静态内部类创建对象的格式

// 格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
Outer.Inner in =  new Outer.Inner();

静态内部类的访问拓展

1、静态内部类中是否可以直接访问外部类的静态成员?

  • 可以,外部类的静态成员只有一份可以被共享访问。

2、静态内部类中是否可以直接访问外部类的实例成员?

  • 不可以的,外部类的实例成员必须用外部类对象访问。

静态内部类的使用场景、特点、访问总结

  • 如果一个类中包含了一个完整的成分,如汽车类中的发动机类。
  • 特点、使用与普通类是一样的,类有的成分它都有,只是位置在别人里面而已。
  • 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。
  • 注意:开发中实际上用的还是比较少。

11.3、内部类之二:成员内部类[了解]

什么是成员内部类?

  • 无static修饰,属于外部类的对象。
  • JDK16之前,成员内部类中不能定义静态成员,JDK 16开始也可以定义静态成员了。
public class Outer {
    // 成员内部类
    public class Inner {
        
    }
}

成员内部类创建对象的格式

//格式:外部类名.内部类名 对象名 = new  外部类构造器.new 内部类构造器();
Outer.Inner in =  new Outer().new Inner();

成员内部类的访问拓展

1、成员内部类中是否可以直接访问外部类的静态成员?

  • 可以,外部类的静态成员只有一份可以被共享访问。

2、成员内部类的实例方法中是否可以直接访问外部类的实例成员?

  • 可以,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员

面试

class People{
    private int heartbeat = 150;

    /**
       成员内部类
     */
    public class Heart{
        private int heartbeat = 110;

        public void show(){
            int heartbeat = 78;
            System.out.println(heartbeat); // 78
            System.out.println(this.heartbeat); // 110
            System.out.println(People.this.heartbeat); // 150
        }
    }
}

11.4、局部内部类[了解]

局部内部类 (鸡肋语法,了解即可)

  • 局部内部类放在方法、代码块、构造器等执行体中。
  • 局部内部类的类文件名为: 外部类$N内部类.class。
  • 可以定义抽象类,接口(jdk15 开始支持)

其实就是在方法里写一个类,这种做法我们开发是尽量避免使用的。

11.5、匿名内部类[重点]

11.5.1、概述

匿名内部类

  • 本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等。
  • 作用:方便创建子类对象,最终目的为了简化代码编写。
// new 类|抽象类名|或者接口名() {
// 		重写方法;
// };
Animal a = new Animal() {
    public void run() {
        
    }
};
a. run();

特点总结

  • 匿名内部类是一个没有名字的内部类。
  • 匿名内部类写出来就会产生一个匿名内部类的对象。
  • 匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。

代码演示

/**
      目标:学习匿名内部类的形式和特点。
 */
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal() {
            @Override
            public void run() {
                System.out.println("老虎跑的块~~~");
            }
        };
        a.run();
    }
}

//class Tiger extends Animal{
//    @Override
//    public void run() {
//        System.out.println("老虎跑的块~~~");
//    }
//}

abstract class Animal{
    public abstract void run();
}

输出结果

老虎跑的块~~~

11.5.2、匿名内部类常见使用形式

需求:某个学校需要让老师,学生,运动员一起参加游泳比赛

/**
    目标:掌握匿名内部类的使用形式(语法)
 */
public class Test2 {
    public static void main(String[] args) {
        Swimming s = new Swimming() {
            @Override
            public void swim() {
                System.out.println("学生快乐的自由泳🏊‍");
            }
        };
        go(s);

        System.out.println("--------------");

        Swimming s1 = new Swimming() {
            @Override
            public void swim() {
                System.out.println("老师泳🏊的贼快~~~~~");
            }
        };
        go(s1);

        System.out.println("--------------");

        go(new Swimming() {
            @Override
            public void swim() {
                System.out.println("运动员🏊的贼快啊~~~~~");
            }
        });

        // 简化写法
//        go(() -> System.out.println("运动员🏊的贼快啊~~~~~"));
        
    }

    /**
       学生 老师 运动员可以一起参加游泳比赛
     */
    public static void go(Swimming s){
        System.out.println("开始。。。");
        s.swim();
        System.out.println("结束。。。");
    }
}


interface Swimming{
    void swim();
}

输出结果

开始。。。
学生快乐的自由泳🏊‍
结束。。。
--------------
开始。。。
老师泳🏊的贼快~~~~~
结束。。。
--------------
开始。。。
运动员🏊的贼快啊~~~~~
结束。。。

总结

匿名内部类可以作为方法的实际参数进行传输。

简化写法(提前理解)

go(new Swimming() {
            @Override
            public void swim() {
                System.out.println("运动员🏊的贼快啊~~~~~");
            }
        });

        // 简化写法
//        go(() -> System.out.println("运动员🏊的贼快啊~~~~~"));

11.5.3、匿名内部类真实使用场景

代码演示

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
   目标:通过GUI编程 理解匿名内部类的真实使用场景
 */
public class Test3 {
    public static void main(String[] args) {
        // 1、创建窗口
        JFrame win = new JFrame("登录界面");
        JPanel panel = new JPanel();
        win.add(panel);

        // 2、创建一个按钮对象
        JButton btn = new JButton("登录");

        // 注意:讲解匿名内部类的使用
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(win, "点我一下,说明爱我!");
            }
        });

//        btn.addActionListener( e ->  JOptionPane.showMessageDialog(win, "别说话,吻我!!") );


        // 3、把按钮对象添加到桌布上展示
        panel.add(btn);

        // 4、展示窗口
        win.setSize(400, 300);
        win.setLocationRelativeTo(null);
        win.setVisible(true);

    }
}

总结

开发中不是我们主动去定义匿名内部类的,而是别人需要我们写或者我们可以写的时候才会使用。匿名内部类的代码可以实现代码进一步的简化

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值