Java基础

Java帝国的诞生

C & C++

  • 1972年C诞生
    • 贴近硬件,运行极快,效率极高
    • 操作系统,编译器,数据库,网络系统等
    • 指针和内存管理
  • 1982年C++诞生
    • 面向对象
    • 兼容C
    • 图形领域、游戏等

反抗

我们要建立一个新的语言

  • 语法有点像C
  • 没有指针
  • 没有内存管理
  • 真正的可移植性,编写一次,到处运行
  • 面向对象
  • 类型安全
  • 高质量的类库
  • ……

Java初生

  • 1995年的网页简单而粗糙,缺乏互动性

  • 图形页面的程序(Applet)

  • Java2 标准版(J2SE):占领桌面

  • Java2 移动版(J2ME):占领手机

  • Java2 企业版(J2EE):占领服务器

Java发展

  • 基于Java开发了巨多的平台,系统,工具
    • 构建工具:Ant,Maven,Jekins
    • 应用服务器:Tomcat、Jetty、Jboss,Websphere,weblogic
    • Web开发:Struts,Spring,Hibernate,myBatis
    • 开发工具:Eclipse,NetBeans,IDEA,Jbuilder
    • ……
  • 2006:Hadoop(大数据领域)
  • 2008:Android(手机端)

“三高”:高可用、高性能、高并发

Java特性和优势

  • 简单性
  • 面向对象
  • 可移植性
  • 高性能
  • 分布式
  • 动态性
  • 多线程
  • 安全性
  • 健壮性

Java三大版本

Write Once、Run Anywhere

  • JavaSE:标准版(桌面程序,控制台开发……)
  • JavaME:嵌入式开发(手机,小家电……)
  • JavaEE:企业级开发(web端,服务器开发……)

JDK、JRE、JVM

  • JDK:Java Development Kit
  • JRE:Java Runtime Environment
  • JVM:Java Virtual Machine

Java开发环境搭建

JDK下载与安装

卸载JDK

  1. 删除Java的安装目录
  2. 删除系统环境变量的JAVA_HOME
  3. 删除系统环境变量path下关于Java的目录

安装JDK

  1. 百度搜索JDK8,找到下载地址
  2. 同意协议
  3. 下载电脑对应的版本
  4. 双击安装JDK
  5. 记住安装的路径

配置环境变量

  1. 我的电脑 --> 右键属性
  2. 高级系统设置 --> 环境变量 --> JAVA_HOME
  3. 配置path变量
  4. 测试JDK是否安装

HelloWorld及简单语法规则

  1. 新建一个Java文件

    • 文件后缀名为.java
    • 【注意】系统可能没有显示文件后缀名,我们需要手动打开
  2. 编写代码

    public class Hello {
        public static void main(String[] args) {
        	System.out.println("Hello World");
       }
    }
    
  3. 编译javac java文件,会生成一个class文件

  4. 运行class文件

可能出现的问题

  1. 每个单词的大小不能出现问题,Java是严格区分大小写的
  2. 尽量使用英文
  3. 文件名和类名必须保持一致,并且首字母大写
  4. 符号使用了中文的

Java基础语法

注释

  • 平时我们编写代码,在代码量比较少的时候,我们还可以看懂自己写的,但是当项目结构一旦复杂起来,我们就需要用到注释了
  • 注释并不会被执行,是给我们写代码的人看的
  • 书写注释是一个非常好的习惯

Java中的注释有三种

  1. 单行注释
  2. 多行注释
  3. 文档注释

标识符

关键字

Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符

注意点

  • 所有的标识符都应该以字母(A-Z或者a-z)、美元符号($)、或者下划线(_)开始
  • 首字符之后可以是字母(A-Z或者a-z)、美元符号($)、下划线(_)或数字的任何字符组合
  • 不能使用关键字作为变量名或方法名
  • 标识符是大小写敏感
  • 合法的标识符举例:age、$salary、_value、__1_value
  • 非法的标识符举例:123abc、-salary、#abc
  • 可以使用中文命名,但是一般不建议这样去使用,也不建议使用拼音,很low

数据类型

  • 强类型语言
    • 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用
  • 弱类型语言

Java的数据类型分为两大类

  • 基本数据类型(primitive type)
  • 引用数据类型(reference type)

什么是字节

  • 位(bit):是计算机内部数据存储的最小单位,11001100是一个八位二进制数
  • 字节(byte):是计算机中数据处理的基本单位,习惯上用大写B来表示
  • 1B(byte,字节) = 8bit(位)
  • 字符:是指计算机中使用的字母、数字、字和符号

数据类型扩展

public class Solution {
    public static void main(String[] args) {
        // 整数扩展:进制  二进制0b   八进制0    十进制    十六进制0x
        int i1 = 10;
        int i2 = 010;   // 八进制0
        int i3 = 0x10;  // 十六进制0x   0~9 A~F

        System.out.println(i1);
        System.out.println(i2);
        System.out.println(i3);

        System.out.println("==========================");
        // 浮点数扩展    银行业务怎么表示?   使用BigDecimal 数学工具类
        // float、double     有限  离散
        // 最好避免使用浮点数进行比较
        float f = 0.1f;
        double d = 1.0 / 10;
        System.out.println(f);  // 0.1
        System.out.println(d);  // 0.1
        System.out.println(f == d); // false

        float f1 = 23131312312312313f;
        float f2 = f1 + 1;
        System.out.println(f1 == f2);   // true

        System.out.println("==========================");
        // 字符扩展
        // 所有的字符本质还是数字
        // 编码 Unicode 表:(97 = a  65 = A)  2字节   0 - 65536
        char c1 = '\u0061';
        System.out.println(c1);  // a

        // 转义字符
        // \t   制表符
        // \n   换行
        System.out.println("Hello\tworld");
        System.out.println("Hello\nworld");

        System.out.println("==========================");
        // 对象   从内存分析
        String s1 = new String("Hello World");
        String s2 = new String("Hello World");
        System.out.println(s1 == s2);   // false

        String s3 = "Hello World";
        String s4 = "Hello World";
        System.out.println(s3 == s4);   // true
    }
}

类型转换

  • 由于Java是强类型语言,所以要进行有些运算的时候,需要用到类型转换

    • 低 ------------------------------------------------------> 高
    • byte,short,char -> int -> long -> float -> double
  • 运算中,不同类型的数据先转化为同一类型,然后进行运算

  • 强制类型转换 (类型)变量由高到低

  • 自动类型转换 (类型)变量由低到高

注意点

  1. 不能把布尔值进行转换
  2. 不能把对象类型转换为不相干的类型
  3. 在把高容量转换到低容量的时候,强制转换
  4. 转换的时候可能存在内存溢出,或者精度问题
public class Solution {
    public static void main(String[] args) {
        // 操作比较大的数的时候,注意溢出问题
        // JDK 7新特性,数字之间可以使用下划线分割
        int money = 10_0000_0000;
        int year = 20;
        int total = money * year;
        System.out.println(total);  // -1474836480  计算的时候溢出了

        long total2 = money * year;
        System.out.println(total2); // -1474836480  默认是int,转换之前已经存在问题了

        long total3 = money * (long)year;
        System.out.println(total3); // 20000000000  先把一个数转换为long
    }
}

变量

  • 变量是什么:就是可以变化的量
  • Java是一种强类型语言,每个变量都必须声明其类型
  • Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域
//数据类型 变量名 = 值;可以使用逗号隔开来声明多个同类型的变量
type varName [= value] [{, varName [= value]}];

注意事项

  1. 每个变量都有类型,类型可以是基本类型,也可以是引用类型
  2. 变量名必须是合法的标识符
  3. 变量声明是一条完整的语句,因此每一个声明都必须以分号结束

变量作用域

  • 类变量
  • 实例变量
  • 局部变量
public class Variable {
    static int allClicks = 0;   // 类变量
    String str = "hello world"; // 实例变量
    
    public static void main(String[] args) {
        int i = 0;  // 局部变量
    }
}

变量的命名规范

  • 所有变量、方法、类名:见名知意
  • 类成员变量:首字母小写和驼峰原则:monthSalary
  • 局部变量:首字母和驼峰原则
  • 常量:大写字母和下划线:MAX_VALUE
  • 类名:首字母大写和驼峰原则:Man,GoodMan
  • 方法名:首字母小写和驼峰原则:run(),runRun()

常量

  • 常量(Constant):初始化(initialize)后不能再改变值!不会变动的值
  • 所谓常量可以理解成一种特殊的变量,它的值被设定后,在程序运行过程中不允许被改变
  • 常量名一般使用大写字符
final 常量名 = 值;
final double PI = 3.14;

运算符

Java语言支持如下运算符

  • 算术运算符:+、-、*、/、%、++、--

    • (前)++ :先自增1,后运算
    • (后)++ :先运算,后自增1
    • (前)-- :先自减1,后运算
    • (后)-- :先运算,后自减1
    • 连接符:+:只能在String与其他数据类型变量之间使用
  • 赋值运算符:=、+=、-=、*=、/=

    • 运算的结果不会改变变量本身的数据类型
    • 开发中,如果希望变量实现+2的操作,推荐使用:num += 2;
    • 开发中,如果希望变量实现+1的操作,推荐使用num++
  • 关系运算符:>、<、>=、<=、==、!= instanceof

    • 比较运算符的结果是boolean类型
    • > < >= <= :只能使用在数值类型的数据之间
    • == 和 != :不仅可以使用在数值类型数据之间,还可以使用在其他引用类型变量之间
  • 逻辑运算符:&&、||、!

    • 逻辑运算符操作的都是boolean类型的变量。而且结果也是boolean类型
    • 区分& 与 &&
      • 相同点1:& 与 && 的运算结果相同
      • 相同点2:当符号左边是true时,二者都会执行符号右边的运算
      • 不同点:当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。
    • 区分:| 与 ||
      • 相同点1:| 与 || 的运算结果相同
      • 相同点2:当符号左边是false时,二者都会执行符号右边的运算
      • 不同点3:当符号左边是true时,|继续执行符号右边的运算,而||不再执行符号右边的运算
  • 位运算符:&、|、^、~、>>、<<、>>>(了解)

    • 【面试题】你能否写出最高效的2 * 8的实现方式?
      答案:2 << 3 或 8 << 1
      • 位运算符操作的都是整型的数据
      • <<:在一定范围内,每向左移1位,相当于 * 2
      • >>:在一定范围内,每向右移1位,相当于 / 2
  • 三元运算符:(条件表达式) ? 表达式1 : 表达式2

    • 条件表达式的结果为boolean类型
    • 根据条件表达式真或假,决定执行表达式1,还是表达式2.
      • 如果表达式为true,则执行表达式1。
      • 如果表达式为false,则执行表达式2。
    • 表达式1 和表达式2要求是一致的。
    • 三元运算符可以嵌套使用

包机制

  • 为了更好的组织类,Java提供了包机制,用于区别类名的命名空间

  • 包语句的语句格式为

    package pkg1[. pkg2[. pkg3...]];
    
  • 一般利用公司域名倒置作为包名

  • 为了能够使用某一个包的成员,我们需要在Java程序中明确导入该包。使用"import"语句可完成此功能

    import package1[.package2...].(classname|*);
    

Java流程控制

Scanner对象

  • 之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入。java.util.Scanner是Java5的新特征,我们可以通过Scanner类来获取用户的输入

  • 基本语法

    Scanner sc = new Scanner(System.in);
    
  • 通过Scanner类的next()与nextLine()方法获取输入的字符串,在读取前我们一般需要使用hasNext()与hasNextLine()判断是否还有输入的数据

package com.mingmi.demo;

import java.util.Scanner;

public class Solution {

    public static void main(String[] args) {

        // 创建一个扫描器对象,用于接收键盘数据
        Scanner sc = new Scanner(System.in);

//        System.out.println("使用next方式接收:");
//
//        // 判断用户有没有输入字符串
//        if (sc.hasNext()) {
//            // 使用next方式接收
//            String str = sc.next();
//            System.out.println("输出的内容为:" + str);
//        }

        System.out.println("使用nextLine方式接收:");

        // 判断用户有没有输入字符串
        if (sc.hasNextLine()) {
            // 使用nextLine方式接收
            String str = sc.nextLine();
            System.out.println("输出的内容为:" + str);
        }

        // 凡是属于IO流的类如果不关闭会一直占用资源,要养成好习惯用完就关掉
        sc.close();
    }
}
  • next()
    • 一定要读取到有效字符后才可以结束输入
    • 对输入有效字符之前遇到的空白,next()方法会自动将其去掉
    • 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符
    • next()不能得到带有空格的字符串
  • nextLine()
    • 以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符
    • 可以获得空白

顺序结构

  • Java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行
  • 顺序结构是最简单的算法结构
  • 语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个执行的处理步骤组成的,它是任何一种算法都离不开的一种基本算法结构

选择结构

if单选择结构

我们很多时候需要去判断一个东西是否可行,然后我们才去执行,这样一个过程在程序中用if语句来表示

语法

if (布尔表达式) {
    // 如果布尔表达式为true将执行的语句
}

if双选择结构

那现在有个需求,公司要收购一个软件,成功了,给人支付100万元,失败了,自己找人开发。这样的需求用一个if就搞不定了,我们需要有两个判断,需要一个双选择结构,所以就有了if-else结构

语法

if (布尔表达式) {
    // 如果布尔表达式的值为true
} else {
    // 如果布尔表达式的值为false
}

if多选择结构

我们发现刚才的代码不符合实际情况,真实的情况还可能存在ABCD,存在区间多级判断。比如90-100就是A,80-90就是B...等等,在生活中我们很多时候的选择也不仅仅只有两个,所以我们需要一个多选择结构来处理这类问题!

语法

if (布尔表达式1) {
    // 如果布尔表达式1的值为true执行代码
} else if (布尔表达式2) {
    // 如果布尔表达式2的值为true执行代码
} else if (布尔表达式3) {
    // 如果布尔表达式3的值为true执行代码
} else {
    // 如果以上布尔表达式都不为true执行代码
}

嵌套的if结构

使用嵌套的if...else语句是合法的。也就是说你可以在另一个if或者else if语句中使用if或者else if语句。你可以像if语句一样嵌套else if...else

语法

if (布尔表达式1) {
    // 如果布尔表达式1的值为true执行代码
    if (布尔表达式2) {
        // 如果布尔表达式2的值为true执行代码
    }
}

switch多选择结构

  • 多选择结构还有一个实现方式就是switch case语句
  • switch case语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支
  • switch语句中的变量类型可以是
    • byte、short、int或者char
    • 从Java SE 5开始switch支持枚举类型
    • 从Java SE 7开始switch支持字符串String类型,同时case标签必须为字符串常量或字面量
switch (expression) {
    case value :
        // 语句
        break;	// 可选
    case value :
        // 语句
        break;	// 可选
    // 你可以有任意数量的case语句
    default :	// 可选
        // 语句
}

循环结构

while循环

  • while是最基本的循环,它的结构为

    while (布尔表达式) {
        // 循环内容
    }
    
  • 只要布尔表达式为true,循环就会一直执行下去

  • 我们大多数情况是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环

  • 少部分情况需要循环一直执行,比如服务器的请求相应监听等

  • 循环条件一直为true就会造成无限循环【死循环】,我们正常的业务编程中应该尽量避免死循环。会影响程序性能或者造成程序卡死崩溃

  • 思考:计算1+2+3+...+100?

    public class Solution {
        public static void main(String[] args) {
            // 计算1+2+3+...+100
            int i = 0;
            int sum = 0;
    
            while (i <= 100) {
                sum = sum + i;
                i++;
            }
    
            System.out.println(sum);
        }
    }
    

do...while循环

  • 对于while语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次

  • do...while循环和while循环相似,不同的是,do...while循环至少会执行一次

    do {
        // 代码语句
    } while (布尔表达式);
    
  • while和do...while的区别

    • while先判断后执行。do...while是先执行后判断
    • do...while总是保证循环体会被至少执行一次

for循环

  • 虽然所有循环结构都可以用while或者do...while表示,但Java提供了另一种语句——for循环,使一些循环结构变得更加简单

  • for循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构

  • for循环执行的次数是在执行前就确定的。语法格式如下

    for (初始化; 布尔表达式; 更新) {
        // 代码语句
    }
    
  • 练习1:计算0到100之间的奇数和偶数的和

    public class Solution {
        public static void main(String[] args) {
            // 练习1:计算0到100之间的奇数和偶数的和
            int oddSum = 0;     // 奇数和
            int evenSum = 0;    // 偶数和
    
            for (int i = 0; i < 100; i++) {
                if (i % 2 != 0) {
                    oddSum += i;
                } else {
                    evenSum += i;
                }
            }
    
            System.out.println("奇数的和为:" + oddSum);
            System.out.println("偶数的和为:" + evenSum);
        }
    }
    
  • 练习2:用while或for循环输出1-1000之间能被5整除的数,并且每行输出3个

    public class Solution {
        public static void main(String[] args) {
            // 练习2:用while或for循环输出1-1000之间能被5整除的数,并且每行输出3个
    
            int count = 0;  // 记录输出的个数
    
            for (int i = 0; i < 1000; i++) {
                if (i % 5 == 0) {
                    System.out.print(i + "\t");
                    count++;
                    if (count % 3 == 0) {
                        System.out.println();
                    }
                }
            }
        }
    }
    
  • 练习3:打印九九乘法表

    public class Solution {
        public static void main(String[] args) {
            // 练习3:打印九九乘法表
            for (int i = 1; i <= 9; i++) {
                for (int j = 1; j <= i; j++) {
                    System.out.print(j + " * " + i + " = " + (i * j) + "\t");
                }
                System.out.println();
            }
        }
    }
    

关于for循环有以下几点说明

  • 最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句。
  • 然后。检测布尔表达式的值。如果为true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句。
  • 执行一次循环后,更新循环控制变量(迭代因子控制循环变量的增减)。
  • 再次检测布尔表达式。循环执行上面的过程

增强for循环

  • 这里我们先只是见一面,做个了解,之后数组我们重点使用

  • Java 5引入了一种主要用于数组或集合的增强型for循环

  • Java增强for循环语法格式如下

    for (声明语句 : 表达式) {
        // 代码语句
    }
    
  • 声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等

  • 表达式:表达式是要访问的数组名,或者是返回值为数组的方法

break、continue

  • break在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。(break语句也在switch语句中使用)
  • continue语句用在循环语句中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定
  • 关于goto关键字
    • goto关键字很早就在程序设计语言中出现。尽管goto仍是Java的一个保留字,但并未在语言中得到正式使用;Java没有goto。然而,在break和continue这两个关键字的身上,我们仍然能看出一些goto的影子——带标签的break和continue
    • “标签”是指后面跟一个冒号的标识符,例如:label:
    • 对Java来说唯一用到标签的地方是在循环语句之前。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环,由于break和continue关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方

Java方法详解

何谓方法

  • Java方法是语句的集合,它们在一起执行一个功能
    • 方法是解决一类问题的步骤的有序组合
    • 方法包含于类或对象中
    • 方法在程序中被创建,在其他地方被引用
  • 设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成一个功能,这样利于我们后期的扩展

方法的定义

  • Java的方法类似于其他语言的函数,是一段用来完成特定功能的代码片段,一般情况下,定义一个方法包含以下语法

  • 方法包含一个方法头和一个方法体。下面是一个方法的所有部分:

    • 修饰符:这是可选的,告诉编译器如何调用改方法。定义了该方法的访问类型
    • 返回值类型:方法可能会有返回值。returnValueType是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值。在这种情况下,returnValueType是关键字void
    • 方法名:是方法的实际名称,方法名和参数列表共同构成方法签名
    • 参数类型:参数像是一个占位符,当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数
      • 形式参数:在方法被调用时用于接收外界输入的数据
      • 实参:调用方法时实际传给方法的数据
    • 方法体:方法体包含具体的语句,定义该方法的功能
    修饰符 返回值类型 方法名(参数类型 参数名) {
        方法体
    }
    

方法调用

  • 调用方法:对象名.方法名(实参列表)

  • Java支持两种调用方法的方式,根据方法是否返回值来选择

  • 当方法返回一个值的时候,方法调用通常被当做一个值。例

    int larger = max(30, 40);
    
  • 如果方法返回值是void,方法调用一定是一条语句

    System.out.println("Hello world!");
    

方法的重载

  • 重载就是在一个类中,有相同的函数名称,但形参不同的函数
  • 方法的重载的规则
    • 方法名称必须相同
    • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)
    • 方法的返回类型可以相同也可以不同
    • 仅仅返回类型不同不足以成为方法的重载
  • 实现理论:方法名称相同时,编译器会根据调用方法的参数个数,参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错

可变参数

  • JDK 1.5开始,Java支持传递同类型的可变参数给一个方法

  • 在方法声明中,在指定参数类型后加一个省略号(…)

  • 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明

    public class Solution {
        public static void main(String[] args) {
            // 调用可变参数的方法
            printMax(34, 3, 2, 3, 56.5);
            printMax(new double[]{1, 2, 3});
        }
    
        public static void printMax(double ... numbers) {
            if (numbers.length == 0) {
                System.out.println("No argument passed");
                return;
            }
    
            double result = numbers[0];
    
            for (int i = 1; i < numbers.length; i++) {
                if (numbers[i] > result) {
                    result = numbers[i];
                }
            }
            System.out.println("The max value is: " + result);
        }
    }
    

递归

  • 递归就是:A方法调用A方法!就是自己调用自己

  • 利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合

  • 递归结构包括两个部分

    • 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环
    • 递归体:什么时候需要调用自身方法
  • 举例

    // 例1:计算1-n之间所有自然数的和
    public int getSum(int n) {// 3
    	if (n == 1) {
    		return 1;
    	} else {
    		return n + getSum(n - 1);
    	}
    }
    
    // 例2:计算1-n之间所有自然数的乘积:n!
    public int getSum1(int n) {
    	if (n == 1) {
    		return 1;
    	} else {
    		return n * getSum1(n - 1);
    	}
    }
    	
    // 例3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
    // 其中n是大于0的整数,求f(10)的值。
    public int f(int n) {
    	if (n == 0) {
    		return 1;
    	} else if (n == 1) {
    		return 4;
    	} else {
    		return 2 * f(n - 1) + f(n - 2);
    	}
    }
    

简易计算器

package com.mingmi.demo;

import java.util.Scanner;

public class Solution {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        while (true) {

            System.out.println("请输入第一个数:");
            double a = sc.nextDouble();

            System.out.println("请输入要进行的操作(+、-、*、/):");
            String operate = sc.next();

            System.out.println("请输入第二个数:");
            double b = sc.nextDouble();

            switch (operate) {
                case "+":
                    add(a, b);
                    break;
                case "-":
                    reduce(a, b);
                    break;
                case "*":
                    ride(a, b);
                    break;
                case "/":
                    except(a, b);
                    break;
                default:
                    System.out.println("请输入正确的操作!");
                    sc.close();
                    return;
            }
        }
    }

    public static void add(double a, double b) {
        System.out.println(a + " + " + b + " 的值为:" +(a + b));
    }

    public static void reduce(double a, double b) {
        System.out.println(a + " - " + b + " 的值为:" +(a - b));
    }

    public static void ride(double a, double b) {
        System.out.println(a + " * " + b + " 的值为:" +(a * b));
    }

    public static void except(double a, double b) {
        if (b == 0) {
            System.out.println("除数不能为0");
        } else {
            System.out.println(a + " / " + b + " 的值为:" + (a / b));
        }
    }
}

数组

数组的定义

  • 数组是相同类型数据的有序集合
  • 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成
  • 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们

数组声明创建

  • 首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法

    dataType[] arrayRefVar;		// 首选的方法
    或
    dataType arrayRefVar[];		// 效果相同,但不是首选方法
    
  • Java语言使用new操作符来创建数组,语法如下

    dataType[] arrayRefVar = new dataType[arraySize];
    
  • 数组的元素是通过索引访问的,数组索引从0开始

  • 获取数组长度:array.length

内存分析

三种初始化

  • 静态初始化

    int[] a = {1, 2, 3};
    Man[] mans = {new Man(1, 1), new Man(2, 2)};
    
  • 动态初始化

    int[] a = new int[2];
    a[0] = 1;
    a[1] = 2;
    
  • 数组的默认初始化

    • 数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化

数组的四个基本特点

  • 其长度是确定的。数组一旦被创建,它的大小就是不可以改变的
  • 其元素必须是相同类型,不允许出现混合类型
  • 数组中的元素可以是任何数据类型,包括基本类型和引用类型
  • 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的

数组边界

  • 下标的合法区间:[0, length - 1],如果越界就会报错

    public static void main(String[] args) {
        int[] a = new int[2];
        System.out.println(a[2]);
    }
    
  • ArrayIndexOutOfBoundsException:数组下标越界异常!

  • 小结

    • 数组是相同数据类型(数据类型可以为任意类型)的有序集合
    • 数组也是对象。数组元素相当于对象的成员变量
    • 数组长度是确定的,不可变的。如果越界,则报:ArrayIndexOutOfBounds

数组使用

  • 普通的for循环

    public class Solution {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3, 4, 5};
    
            // 打印全部的数组元素
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
    
            // 计算所有元素的和
            int sum = 0;
            for (int i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            System.out.println("和为:" + sum);
    
            // 查找最大元素
            int max = arr[0];
            for (int i = 1; i < arr.length; i++) {
                if (arr[i] > max) {
                    max = arr[i];
                }
            }
            System.out.println("最大数为:" + max);
        }
    }
    
  • for-each循环

    public class Solution {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3, 4, 5};
    
            // JDK 1.5新增    没有下标
            for (int i : arr) {
                System.out.println(i);
            }
        }
    }
    
  • 数组作方法入参

    public class Solution {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3, 4, 5};
    
            printArray(arr);
        }
    
        // 打印数组元素
        public static void printArray(int[] arr) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
        }
    }
    
  • 数组作返回值

    public class Solution {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3, 4, 5};
    
            int[] reverse = reverse(arr);
            printArray(reverse);
        }
    
        // 反转数组
        public static int[] reverse(int[] arr) {
            int[] result = new int[arr.length];
    
            // 反转的操作
            for (int i = 0; i < arr.length; i++) {
                result[i] = arr[arr.length - i - 1];
            }
    
            return result;
        }
    
        // 打印数组元素
        public static void printArray(int[] arr) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
        }
    }
    

多维数组

  • 多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组

  • 二维数组

    以下二维数组a可以看成一个两行五列的数组

    int[][] a = new int[2][5];
    
  • 多维数组的使用

    public class Solution {
        public static void main(String[] args) {
            /*
                1, 2    arr[0]
                2, 3    arr[1]
                3, 4    arr[2]
                4, 5    arr[3]
             */
            int[][] arr = {{1, 2}, {2, 3}, {3, 4}, {4, 5}};
    
            for (int i = 0; i < arr.length; i++) {
                for (int j = 0; j < arr[i].length; j++) {
                    System.out.print(arr[i][j] + "\t");
                }
                System.out.println();
            }
        }
    }
    

Arrays类

  • 数组的工具类java.util.Arrays

  • 由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作

  • Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而“不用”使用对象来调用(注意:是“不用”而不是“不能”)

  • 具体有以下常用功能

    • 给数组赋值:通过fill方法
    • 对数组排序:通过sort方法,按升序
    • 比较数组:通过equals方法比较数组中元素值是否相等
    • 查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作
    //1.boolean equals(int[] a, int[] b):判断两个数组是否相等。
    int[] arr1 = new int[]{1, 2, 3, 4};
    int[] arr2 = new int[]{1, 3, 2, 4};
    boolean isEquals = Arrays.equals(arr1, arr2);
    System.out.println(isEquals);
    		
    //2.String toString(int[] a):输出数组信息。
    System.out.println(Arrays.toString(arr1));
    		
    			
    //3.void fill(int[] a, int val):将指定值填充到数组之中。
    Arrays.fill(arr1, 10);
    System.out.println(Arrays.toString(arr1));
    		
    //4.void sort(int[] a):对数组进行排序。
    Arrays.sort(arr2);
    System.out.println(Arrays.toString(arr2));
    		
    //5.int binarySearch(int[] a, int key)
    int[] arr3 = new int[]{-98, -34, 2, 34, 54, 66, 79, 105, 210, 333};
    int index = Arrays.binarySearch(arr3, 210);
    if(index >= 0){
    	System.out.println(index);
    }else{
    	System.out.println("未找到");
    }
    

冒泡排序

  • 冒泡的代码为两层循环。外层冒泡轮数,内层依次比较

  • 我们看到嵌套循环,应该立马就可以得出这个算法的时间复杂度为O(n^2)

    package com.mingmi.demo;
    
    import java.util.Arrays;
    
    public class Solution {
        public static void main(String[] args) {
            int[] arr = new int[]{34, 5, 22, -98, 6, -76, 0, -3};
            sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    
        /*
            冒泡排序
            1. 比较数组中,两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
            2. 每一次比较,都会产生出一个最大(或最小)的数
            3. 下一轮则可以少一次排序
            4. 依次循环,知道结束
         */
        public static void sort(int[] arr) {
            // 外层循环,判断我们要走多少次
            for (int i = 0; i < arr.length - 1; i++) {
                // 内层循环,判断这两个数。如果第一个数比第二个数大,则交换位置
                for (int j = 0; j < arr.length - i - 1; j++) {
                    if (arr[j] > arr[j + 1]) {
                        int temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                    }
                }
            }
        }
    }
    

稀疏数组

  • 当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组

  • 稀疏数组的处理方式是

    • 记录数组一共有几行几列,有多少个不同值
    • 把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模
    public class Solution {
        public static void main(String[] args) {
            // 1. 创建一个二维数组 11 * 11      0:没有棋子  1:黑棋    2:白棋
            int[][] arr1 = new int[11][11];
            arr1[1][2] = 1;
            arr1[2][3] = 2;
    
            // 输出原始的数组
            System.out.println("原始的数组为");
            for (int[] ints : arr1) {
                for (int anInt : ints) {
                    System.out.print(anInt + "\t");
                }
                System.out.println();
            }
    
            System.out.println("==================================================");
    
            // 转换为稀疏数组保存
            // 获取有效值的个数
            int sum = 0;
            for (int i = 0; i < 11; i++) {
                for (int j = 0; j < 11; j++) {
                    if (arr1[i][j] != 0) {
                        sum++;
                    }
                }
            }
            System.out.println("有效值的个数为:" + sum);
    
            // 2. 创建一个稀疏数组
            int[][] arr2 = new int[sum + 1][3];
    
            // 给稀疏数组第一行赋值
            arr2[0][0] = 11;
            arr2[0][1] = 11;
            arr2[0][2] = sum;
    
            // 遍历二维数组,将非零的值,存放稀疏数组中
            int count = 0;
            for (int i = 0; i < arr1.length; i++) {
                for (int j = 0; j < arr1[i].length; j++) {
                    if (arr1[i][j] != 0) {
                        count++;
                        arr2[count][0] = i;
                        arr2[count][1] = j;
                        arr2[count][2] = arr1[i][j];
                    }
                }
            }
    
            // 输出稀疏数组
            System.out.println("转换为稀疏数组为");
            for (int i = 0; i < arr2.length; i++) {
                System.out.println(arr2[i][0] + "\t" +
                        arr2[i][1] + "\t" +
                        arr2[i][2]);
            }
    
            System.out.println("==================================================");
    
            // 3. 从稀疏数组还原为原数组
            // 3.1 读取稀疏数组
            int[][] arr3 = new int[arr2[0][0]][arr2[0][1]];
    
            // 3.2 给其中的元素还原它的值
            for (int i = 1; i < arr2.length; i++) {
                arr3[arr2[i][0]][arr2[i][1]] = arr2[i][2];
            }
    
            // 3.3 打印还原后的数组
            System.out.println("还原后的数组为");
            for (int[] ints : arr1) {
                for (int anInt : ints) {
                    System.out.print(anInt + "\t");
                }
                System.out.println();
            }
        }
    }
    

面向对象编程

面向过程 & 面向对象

  • 面向过程思想
    • 步骤清晰简单,第一步做什么,第二步做什么……
    • 面向过程适合处理一些较为简单的问题
  • 面向对象思想
    • 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
    • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
  • 对于描述复杂的事务,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理

什么是面向对象

  • 面向对象编程(Object-Oriented Programming,OOP)的本质就是:以类的方式组织代码,以对象的组织(封装)数据
  • 抽象
  • 三大特征
    • 封装
    • 继承
    • 多态
  • 从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象
  • 从代码运行角度考虑是先有类后有对象。类是对象的模板

类与对象的关系

  • 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一具体的事物
    • 动物、植物、手机、电脑……
    • Person类、Pet类、Car类等,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为
  • 对象是抽象概念的具体实例
    • 张三就是人的一个具体实例,张三家里的旺财就是狗的一个具体实例
    • 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念

创建与初始化对象

  • 使用new关键字创建对象
  • 使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用
  • 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点
    • 必须和类的名字相同
    • 必须没有返回类型,也不能写void

简单小结类与对象

  1. 类与对象
    类是一个模板:抽象,对象是一个具体的实例

  2. 方法
    定义、调用!

  3. 对应的引用
    引用类型:基本类型(8种)
    对象是通过引用来操作的:栈 --> 堆

  4. 属性:字段Field 成员变量
    默认初始化
    数字: 0 0.0
    char: u0000
    boolean:false
    引用: null

    修饰符 属性类型 属性名 = 属性值

  5. 对象的创建和使用
    必须使用new关键字创造对象,构造器
    对象的属性 对象名.属性名
    对象的方法 对象名.方法名

  6. 静态的属性 属性

    动态的行为 方法

封装

  • 我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用
  • 封装(数据的隐藏)
    • 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
  • 记住这句活就够了:属性私有,get/set

继承

  • 继承的本质是对某一批类的对象,从而实现对现实世界更好的建模
  • extends的意思是“扩展”。子类是父类的扩展
  • Java中类只有单继承,没有多继承
  • 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示
  • 子类和父类之间,从意义上讲应该具有“is a”的关系

super

注意点

  1. super调用父类的构造方法,必须在构造方法的第一个
  2. super必须只能出现在子类的方法或者构造方法中
  3. super和this不能同时调用构造方法

VS this

  • 代表的对象不同
    • this:本身调用者这个对象
    • super:代表父类对象的应用
  • 前提
    • this:没有继承也可以使用
    • super:只能在继承条件下才可以使用
  • 构造方法
    • this():本类的构造
    • super():父类的构造

重写

  • 需要有继承 关系,子类重写父类的方法

  • 特点

    1. 方法名必须相同
    2. 参数列表必须相同
    3. 修饰符:范围可以扩大但不能缩小 public > protected > default > private
    4. 抛出的异常:范围可以被缩小但不能被扩大
  • 重写,子类的方法和父类必须要一致;方法体不同

  • 为什么需要重写

    父类的功能,子类不一定需要,或者不一定满足

多态

  • 即同一方法可以根据发送对象的不同而采用多种不同的行为方式

  • 一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多

  • 多态存在的条件

    • 有继承关系
    • 子类重写父类方法
    • 父类引用指向子类对象
  • 注意:多态是方法的多态,属性没有多态性

  • 不能实现多态

    • static 方法,属于类,它不属于实例
    • final 常量
    • private 方法
  • 向上转型:多态

  • 向下转型

    • 为什么使用向下转型
      • 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用
      • 如何才能调用子类特有的属性和方法:使用向下转型
    • 如何实现向下转型
      • 使用强制转换符:()
    • 使用时的注意点
      • 使用强转时,可能出现ClassCastException的异常
      • 为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型
    • instanceof的使用
      • a instanceof A:判断对象a是否是类A的实例。如果是,则返回true;如果不是,返回false
      • 如果 a instanceof A返回true,则a instanceof B也返回true。其中,类B是类A的父类
      • 要求a所属的类与类A必须是子类和父类的关系,否则编译错误

抽象类

  • abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么改方法就是抽象方法;如果修饰类,那么该类就是抽象类
  • 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类
  • 抽象类,不能使用new关键字来创建对象,它是用来让子类继承的
  • 抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的
  • 子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类

接口

  • 普通类:只有具体实现

  • 抽象类:具体实现和规范(抽象方法)都有

  • 接口:只有规范

  • 接口使用interface来定义

  • Java中,接口和类是并列的两个结构

  • 如何定义接口:定义接口中的成员

    • JDK7及以前:只能定义全局常量和抽象方法
      全局常量:public static final的.但是书写时,可以省略不写
      抽象方法:public abstract的
    • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
  • 接口中不能定义构造器的!意味着接口不可以实例化

  • Java开发中,接口通过让类去实现(implements)的方式来使用.

    • 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
    • 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
  • Java类可以实现多个接口 --> 弥补了Java单继承性的局限性
    格式:class AA extends BB implements CC,DD,EE

  • 接口与接口之间可以继承,而且可以多继承

  • 接口的具体使用,体现多态性

  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是...则必须能...”的思想。

  • 接口的本质是契约,就像我们人间的法律一样,制定好后大家都遵守

  • OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如C++、Java、C#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象

内部类

  • 内部类就是在一个类的内部再定义一个类。如果,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了

异常

  • 实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格式不对,你要读取数据库的数据,数据可能是空的等。我们的程序在跑着,内存或者硬盘可能满了等
  • 软件程序在运行过程中,非常可能遇到刚刚提到的这些异常问题,我们叫异常(Exception
  • 异常指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等
  • 异常发生在程序运行期间,它影响了正常的程序执行流程

简单分类

  • 要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常
    • 编译时异常:最具代表的编译时异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略
    • 运行时异常:运行时异常是可能被程序员避免的异常。与编译时异常相反,运行时异常可以在编译时被忽略
    • 错误(Error):错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译时也检查不到

异常体系结构

  • Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类
  • 在Java API中已经定义了许多异常类,这些异常类分为两大类,错误(Error)和异常(Exception

Error

  • Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关
  • Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止
  • 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况

Exception

  • 在Exception分支中有一个重要的子类RuntimeException(运行时异常)
    • ArrayIndexOutOfBoundsException(数组下标越界)
    • NullPointerException(空指针异常)
    • ArithmeticException(算术异常)
    • MissingResourceException(丢失资源)
    • ClassNotFoundException(找不到类)等异常
  • 这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生
  • Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常

自定义异常

  • 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可
  • 在程序中使用自定义异常类,大体可分为以下几个步骤
    1. 创建自定义异常类
    2. 在方法中通过throw关键字抛出异常对象
    3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
    4. 在出现异常方法的调用者中捕获并处理异常
posted @ 2021-07-21 14:14  眳弥  阅读(73)  评论(0)    收藏  举报