java基础编程

java入门

java三大版本

  1. JavaSE(标准版,定位在个人计算机的应用。发展并不好)

  2. JavaEE(企业版,定位在服务器的应用。市场上的主流)

  3. JavaME(微型办,定位在消费性电子产品的应用上。该板块Java市场越来越少,会被安卓取代)

JDK JRE JVM理解

Java程序是运行在JVM(Java虚拟机)上的,在开发程序之前都要配置Java开发环境

JDK:

  • JDK(Java SE Development Kit),Java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等

JRE:

  • JRE( Java Runtime Environment) 、Java运行环境,用于解释执行Java的字节码文件。普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装JDK来编译、调试程序

JVM:

  • JVM(Java Virtual Mechinal),Java虚拟机,是JRE的一部分。它是整个java实现跨平台的最核心的部分,负责解释执行字节码文件,是可运行java字节码文件的虚拟计算机。所有平台的上的JVM向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行

区别和联系:

  • JDK 用于开发,JRE 用于运行java程序 ;如果只是运行Java程序,可以只安装JRE,无序安装JDK
  • JDk包含JRE,JDK 和 JRE 中都包含 JVM
  • JVM 是 java 编程语言的核心并且具有平台独立性

安装开发环境

  • 安装JDK

    • 记住安装到路径
  • 配置环境变量

    • 我的电脑–>高级系统设置–>环境变量
    • 环境变量–>JAVA_HOME(JDK安装根目录)
    • 配置path变量,%JAVA_HOME%\bin%JAVA_HOME%\jre\bin
  • 测试JDK是否安装成功

    • 打开cmd —> java -version
    • 创建.java文件,编写HelloWolrd
    • javac Hello.java:编译java文件,生成.class文件
    • java Hello:执行.class文件

java基础

注释

注释有三种:分为单行注释、多行注释和文档注释

1
2
3
4
5
6
7
8
9
10
// 单行注释

/*
多行注释
*/

/**
* 文档注释
* @Author lrw
*/

标识符

关键字:如class、static、void、if、else等

标识符:

  • 所有标识符应该以字母(A-Z或者a-z)、美元符($)、下划线(_)开始
  • 首字母之后可以是字母(A-Z或者a-z)、美元符($)、下划线(_)或数字的任何字符组合
  • 不能使用关键字作为变量名或方法名
  • 标识符大小写敏感

数据类型

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

java数据类型分为两大类:

  • 基本数据类型
    • 整数类型
      • byte:占一个字节范围:-128-127
      • short:占两个字节范围:-32768-32767
      • int:占四个字节范围:-2147483648-2147483647
      • long:占八个字节范围:-9223372036854775808-9223372036854775807
    • 浮点类型
      • float:占四个字节范围:-3.4E38-3.4E38
      • double:占八个字节范围:-1.7E308-1.7E308
    • 字符类型
    • char:占两个字节范围:0-255
    • boolean:占一位: true或false
  • 引用数据类型
    • 接口
    • 数组
    • 注解

简单来说,所有的非基本数据类型都是引用数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo_01 {
public static void main(String[] args) {
//八大基本数据类型
//整数
int num1 = 1;
byte num2 = 100;
short num3 = 1000;
long num4 = 10000L; //Long类型要在数字后面加个L来区分
//浮点数
float num5 = 50.1F; //小数默认是double类型后面加个F来区分
double num6 = 1.131;
//字符
char name = '中';
//字符串:String不是关键字,是一个类,也不是基本数据类型
//布尔值
boolean flag = true;
}
}

注意:String不是基本数据类型

类型转换

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

运算中,不同类型的数据先转化为同一类型,然后进行运算

由低到高(字节大小):

byte,short,char –> int –> long –> float –> double

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Demo_03 {
public static void main(String[] args) {
int i = 128;
byte b = (byte) i; //内存溢出
System.out.println(i);
System.out.println(b);
//强制转换:(类型)变量名 从高到低
int i2 = 128;
double d = i;
System.out.println(i2);
System.out.println(d);
//自动转换: 从低到高

/*
注意点:
1、不能对布尔值进行转换
2、不能把对象类型转换为不相干的类型
3、在把高容量转换成低容量的时候需要强制转换
4、转换的时候可能存在内存溢出,或者精度问题
*/
System.out.println((int)23.9);
System.out.println((int)24.75F);

char c = 'A';
int i1 = c + 1;
System.out.println((int)c);
System.out.println((char) i1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo_04 {
public static void main(String[] args) {
//操作比较大的数的时候,注意溢出问题
//JDK新特性,数字之间可以用下划线分割
int money = 10_0000_0000;
int years = 20;
int total = money*years; //计算的时候溢出了
System.out.println(total);
long total1 = money*years; //默认是int,转换之前已经溢出了
System.out.println(total1);
long total3 = money*(long)years; //解决问题:先把一个数转换成long
System.out.println(total3);
}
}

变量

java是一种强制类型语言,每个变量都必须声明其类型

java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域

注意事项:

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

变量命名规范:

  • 所有变量、方法、类名:见名知意
  • 类成员变量、局部变量、方法名:首字母小写和驼峰原则
  • 类名:首字母大写和驼峰原则
  • 常量:大写字母和下划线

变量的作用域:

  • 类变量
  • 实例变量
  • 局部变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo_05 {
//类变量
static double salary = 2500;

//实例变量:从属于对象;如果不自行初始化,这个类型的默认值 0 0.0
//布尔值:默认是false
//除了基本类型以外其余的默认值都是null
String name;
int age;

public static void main(String[] args) {
//局部变量;必须声明和初始化值
int a = 1;
System.out.println(a);

//实例变量使用
Demo_05 demo_05 = new Demo_05();
System.out.println(demo_05.age);
System.out.println(demo_05.name);

//类变量使用
System.out.println(salary);
}
}

常量

常量:一种特殊的变量,初始化后不能再改变其值,常量名一般使用大写字母表示

格式:final 常量名 = 值;

1
2
3
4
5
6
7
8
9
public class Demo_06 {

//修饰符不存在先后顺序
static final double PI = 3.14;

public static void main(String[] args) {
System.out.println(PI);
}
}

运算符

运算符种类:

  • 算术运算符:+,-,*,/,%,++,–

  • 赋值运算符:=

  • 关系运算符:> , < , >= , <= , == , != , instanceof

  • 逻辑运算符:&& (与), ||(或), !(非)

  • 位运算符:&,|,^,~,>>,<<,>>>

  • 条件运算符:?,:

  • 扩展赋值运算符:+=,-=,*=,/=

总结:

  • 数值运算有Long类型最后结果也是Long类型,其余都是Int类型
  • 关系运算符和逻辑运算符计算的结果只有true或false
  • a++先赋值再自增,++a先自增再赋值
  • a+=b —> a = a + b
1
2
3
4
5
6
7
8
9
public class Demo_01 {
public static void main(String[] args) {
//短路运算:a&&b 当a为false时,b不会计算
int a = 5;
boolean b = (a<4)&&(a++<4);
System.out.println(b); //false
System.out.println(a); //5
}
}

位运算(了解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Demo02 {
/*
A = 0011 1100
B = 0000 1101
----------------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001 (相同为0相异为1)
~B = 1111 0010
----------------------
<< *2
>> /2

0000 0000 0
0000 0001 1
0000 0010 2
0000 0011 3
0000 0100 4
0000 1000 8
0001 0000 16
*/
public static void main(String[] args) {
System.out.println(2<<3);
System.out.println(16>>3);
}
}

条件运算符

1
2
3
4
5
6
7
8
public class Demo_03 {
public static void main(String[] args) {
int score = 60;
//格式:x?y:z ---> 如果x为true则结果为y,否则为z
String type = score>=60?"及格":"不及格";
System.out.println();
}
}

包机制

用于区分类名的命名空间

本包语法:package 包名

使用:

  • 一般利用公司域名倒置作为包名
  • 使用某一个包的成员,使用import

导包语法:import 包名.类名

总结:

  • package本包语法必须放在最上面
  • import 包名.* 会导入这个包下所有的类

JavaDoc生成文档

javadoc命令可以用来生成自己的API文档

参数信息:

  • @author:作者名
  • @version:版本号
  • @since:指明需要最早使用的jdk版本
  • @param:参数名
  • @return:返回值
  • throws:异常抛出情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author lrw
* @version 1.0
* @since 1.7
*/
public class Doc {
/**
* @param a
* @param b
* @return
* @throws Exception
*/
public int add(int a, int b) throws Exception{
return a + b;
}
}

cmd命令行生成doc文档:javadoc -encoding UTF-8 -charset UTF-8 Doc.java

java流程控制

Scanner对象

程序交互:java给我们提供了一个工具类,我们可以获取用户的输入。java.util.Scanner 是Java5 的新特性,可以通过Scanner类来获取用户的输入

基本语法:Scanner sc = new Scanner(System.in); System.in代表接收用户的输入

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

next():

  • 一定要读取到有效字符后才可以结束输入
  • 对输入有效字符之前遇到的空白,next()会自动将其去掉
  • 只有输入有效字符后才将其后面的输入的空白作为分隔符或者结束符
  • next()不能得到带有空格的字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo_01 {
//创建一个扫描器对象,用于接收键盘数据
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("使用next方式接收:");
//判断用户有没有输入字符串
if(scanner.hasNext()){
//使用next方式接收
String str = scanner.next();
System.out.println("输出的内容为:"+str);
}
//凡是属于IO流的类使用完一定要关闭,会一直占用资源
scanner.close();
}
}

nextLine():

  • 以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符
  • 可以获的空字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo_02 {
//创建一个扫描器对象,用于接收键盘数据
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("使用nextLine方式接收:");
//判断用户有没有输入字符串
if(scanner.hasNext()){
//使用nextline方式接收
String str = scanner.nextLine();
System.out.println("输出的内容为:"+str);
}
//凡是属于IO流的类使用完一定要关闭,会一直占用资源
scanner.close();
}
}

顺序结构

java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行。顺序结构是最简单的算法结构

顺序结构是任何一个算法都离不开的一种基本算法结构

选择结构

if选择结构

if 单选结构:

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

if 双选择结构:

1
2
3
4
5
if(布尔表达式){
//如果布尔表达式为true将执行的语句
}else{
//如果布尔表达式为false将执行的语句
}

if 多选择结构:

  • if 语句至多有一个else 语句,else 语句在所有的else if 语句之后
  • if 语句可以有若干个else if 语句,它们必须在else 语句之前
  • 一旦其中一个else if 或if 语句检测为true,其他语句都将跳过执行,否则执行else
1
2
3
4
5
6
7
8
9
if(布尔表达式 1){
//如果布尔表达式1为true将执行的语句
}else if(布尔表达式 2){
//如果布尔表达式2为true将执行的语句
}else if(布尔表达式 3){
//如果布尔表达式3为true将执行的语句
}else{
//如果布尔表达式为false将执行的语句
}

if 结构嵌套:

1
2
3
4
5
6
if(布尔表达式){
//如果布尔表达式为true将执行的语句
if(布尔表达式){
//如果布尔表达式为true将执行的语句
}
}

switch多选择结构

switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支

  • default在末尾的时候它里面的break可以省略
  • case后面只能跟常量,不能跟变量
  • JDK7新特性,case后面可以是字符串
  • default可以在switch语句的任何位置
  • 切记在case语句中缺少break会出现case穿透现象
  • switch语句遇见break结束,或者程序默认执行到末尾结束
1
2
3
4
5
6
7
8
9
10
11
switch (expression){
case value:
//语句
break; //可选
case value:
//语句
break; //可选
//你可以有任意数量的case语句
default: //可选
//语句
}

case穿透

满足条件case语句块没有break语句,会一直执行下面语句块,直到有break语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo_01 {
public static void main(String[] args) {
char grade = 'B';
switch (grade){
case 'A':
System.out.println("优秀");
break;
case 'B':
System.out.println("良好");
case 'C':
System.out.println("一般");
case 'D':
System.out.println("很差");
break;
default:
System.out.println("未知");
}
}
}

循环结构

while循环

语法:

1
2
3
while (布尔表达式){
//循环体
}
  • 只要buer表达式为true,循环就会一直执行下去
  • 大多数都会让循环停止下来,需要让一个表达式失效的方式结束循环
  • 少部分情况需要循环一直执行,比如服务器的请求响应监听等
  • 尽量避免死循环,会影响程序性能或造成程序崩溃

do while循环

语法:

1
2
3
do {
//循环体
}while (布尔表达式);
  • do while循环总是保证循环体至少执行一次
  • 和while循环的区别在于先执行后判断

for循环

语法:

1
2
3
for (初始化; 布尔表达式; 更新){
//循环体
}
  • for循环是支持迭代的一种通用结构,是最有效、最灵活的循环结构
  • for循环执行的次数是在执行前就确定的

九九乘法表:

1
2
3
4
5
6
7
8
9
10
public class Demo_03 {
public static void main(String[] args) {
for (int i=1; i<=9; i++){
for (int j=1; j<=i; j++){
System.out.print(j+"X"+i+"="+i*j+" ");
}
System.out.println();
}
}
}

增强for循环

语法:

1
2
3
for (声明语句 : 表达式){
//循环体
}
  • JDK5引入的一种主要用于数组或集合的增强型for循环

  • 声明语句声明新的局部变量,该变量的类型必须和数组元素的类型匹配

  • 声明语句作用域限定与循环语句块中,其值与数组元素的值相等

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

break continue

break

break在任何循环语句主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。(break语句也在switch语句中使用)

1
2
3
4
5
6
7
8
9
10
public class Demo_06 {
public static void main(String[] args) {
for (int i=1; i<=20; i++){
if(i==10){
break;
}
System.out.print(i+" ");
}
}
}

continue

continue语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo_05 {
public static void main(String[] args) {

//打印101-150之间所有的质数
outer:for(int i=101; i<=150; i++){
for (int j=2; j<i/2; j++){
if(i % j == 0){
continue outer; //跳到标记处
}
}
System.out.print(i+" ");
}
}
}

java方法

什么是方法

java方法是语句的集合,他们在一起执行一个功能。

  • 方法是解决问题的步骤的有序组合
  • 方法包含于类或对象中
  • 方法在程序中被创建,在其它地方被引用

设计方法的原则:方法的本意是功能块,就是实现某个功能语句块的集合。我们设计方法的时候,最好保持方法的原子性,也就是一个方法只完成一个功能,这样利于我们后期拓展。

方法的定义

java方法类似于其他语言的函数,是一段用来完成特定功能的代码片段。

方法包含一个 方法头和一个方法体:

  • 修饰符:可选,告诉编译器如何调用该方法,定义了该方法的访问类型

  • 返回值类型:方法可能会有返回值,returnValueType是方法返回值的数据类型。有些方法没有返回值,returnValueType是关键字void。

  • 方法名:方法的实际名称,方法名和参数列表共同构成方法签名。

  • 参数类型:参数就像是一个占位符,当方法被调用时,传递值给参数,这个值称为实参或变量。参数列表是指方法的参数类型、顺序和参数个数。参数是可选的,方法可以不包含任何参数。

    • 形式参数:在方法被调用时用于接收外界输入的数据。
    • 实参:调用方法时实际传递给方法的数据。
  • 方法体:方法体包含具体的语句,定义该方法的功能。

格式:

1
2
3
4
5
6
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}

方法的调用

调用方法:对象名.方法名(实参列表),java支持两种调用方法的方式,根据方法是否有返回值来选择。

  • 当方法有返回值时,方法被调用通常被当做一个值。
    • int maxNum = max(20, 30);
  • 如果方法返回值是void,方法调用一定是一条语句。
    • System.out.println(“Hello world”);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo_01 {
public static void main(String[] args) {
//调用方法
int maxNum = max(12, 15);
System.out.println(maxNum);
}
//定义方法
public static int max(int num1, int num2){
if(num1>num2){
return num1;
}else{
return num2;
}
}
}

方法的重载

重载就是在一个类中,有相同的函数名称,但形参不同的函数。

方法重载的规则:

  • 方法名称必须相同
  • 参数列表必须不同(个数不同、类型不同、参数列表排列顺序不同等)
  • 方法的返回值类型可以相同也可以不同
  • 仅仅返回值类型不同不足以成为方法的重载
  • 被重载的方法可以改变访问修饰符
  • 被重载的方法可以声明新的或更广的检查异常

实现理论:

  • 方法名称相同时,编译器会根据调用方法的参数个数、参数类型等逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo_02 {
public static void main(String[] args) {
int num1 = add(1,2);
double num2 = add(1,2);
int num3 = add(1,2,3);
System.out.println(num1+" "+num2+" "+num3);
}
//方法重载
public static int add(int a, int b){
return a+b;
}
public static double add(double a, double b){
return a+b;
}
public static int add(int a, int b, int c){
return a+b+c;
}
}

命令行传参

通过cmd命令传递参数给main方法。

1
2
3
4
5
6
7
public class Demo_03 {
public static void main(String[] args) {
for (int i = 0; i <args.length; i++) {
System.out.println("args["+i+"]:"+args[i]);
}
}
}

可变参数

JDK5开始,java支持传递同类型的可变参数给一个方法。在方法声明中,在指定参数类型后加一个省略号。

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

比较最大数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo_05 {
public static void main(String[] args) {
printMax(1, 2, 3, 4, 5);
printMax(new double[]{1,2,3,4,5});
}
public static void printMax(double... numbers){
if(numbers.length==0){
System.out.println("请传递参数!!");
}else{
double result = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if(numbers[i]>result){
result = numbers[i];
}
}
System.out.println("最大数为:"+result);
}
}
}

递归

递归就是自己调用自己,递归的能力在于用有限的语句来定义对象的无限集合。

递归结构包含两个部分:

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

阶乘:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo_06 {
public static void main(String[] args) {
int num = factorial(5);
System.out.println(num);
}
public static int factorial(int n){
if (n == 1){
return 1;
}else{
return n*factorial(n-1);
}
}
}

java数组

数组的定义

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

数组声明创建

定义数组格式:

  • dataType[] arrayRefVar; 首选
  • dataType arrayRefVar[];

创建数组格式:dataType[] arrayRefVar = new dataType[arraySize];

数组的元素是通过索引访问的,数组索引从0开始

获取数组长度:array.length

数组的三种初始化

静态初始化:

1
2
int[] arr = {1, 2, 3};
Man[] mans = {new Man(1, 1), new Man(2, 2)};

动态初始化:

1
2
3
int[] arr = new int[2];
arr[0] = 1;
arr[1] = 2;

数组默认初始化:

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

数组的内存分析

内存概括

内存是计算机的重要原件,临时数据存储区域,作用是运行程序。我们编写的程序是存放在硬盘中,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。
Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。

java虚拟机的内存划分

为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据的方式和内存管理方式。

JVM的内存划分:

区域名称 作用
寄存器 给CPU使用,和我们开发无关
本地方法栈 JVM在使用操作系统功能的时候使用,和我们开发无关
方法区 储存可以运行的class文件
堆内存 储存对象或者数组,new来创建的,都储存在堆内存中
方法栈 方法运行时使用的内存,比如main方法运行,进入方法栈中执行

数组内存分析

数组的特点

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

数组的边界

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

数组下标越界异常:ArrayIndexOutOfBoundsException

小结:

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

数组的使用

For-Each循环、数组作方法入参、数组作返回值等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo_01 {
public static void main(String[] args) {
int[] arrays = {1, 2, 3, 4, 5};
int[] reverse = reverse(arrays);
print(reverse);
}
//反转数组
public static int[] reverse(int[] arrays){
int[] result = new int[arrays.length];
for (int i = 0, j = arrays.length-1; i <arrays.length; i++, j--) {
result[i] = arrays[j];
}
return result;
}
//打印数组
public static void print(int[] arrays){
for (int array : arrays) {
System.out.print(array+" ");
}
}
}

多维数组

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

二维数组声明创建:int[][] arrays = new int[2][5];

1
2
3
4
5
6
7
8
9
10
11
public class Demo_02 {
public static void main(String[] args) {
int[][] array = {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++){
System.out.print(array[i][j]+" ");
}
System.out.println();
}
}
}

Arrays类

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

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

Arrays类中的方法都是static修饰的静态方法,可以直接通过类名调用。

常用功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Arrays;

public class Demo_03 {
public static void main(String[] args) {
int[] arr = {23, 31, 214, 31, 1, 12341, 1234};
//排序:按升序排序
Arrays.sort(arr);
//打印数组
System.out.println(Arrays.toString(arr));
//填充:给数组每个元素填充
Arrays.fill(arr, 12);
System.out.println(Arrays.toString(arr));
}
}

二分查找法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//二分查找 前提:数组必须有序
public class Demo_09 {
public static void main(String[] args) {
int[] arr = new int[]{10, 15, 17, 20, 45, 90, 101, 1000, 1009, 10008};
int dest = 1000;
int head = 0; //初始首索引
int end = arr.length - 1; //初始末索引
boolean flag = true;
while (head <= end){
int middle = (end + head)/2;
if(arr[middle] == dest){
System.out.println("找到了!在第:"+(middle+1)+"个元素");
flag = false;
break;
}else{
if (arr[middle] > dest){
end = middle - 1;
}else {
head = middle + 1;
}
}
}
if (flag){
System.out.println("没有找到!");
}
}
}

冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法描述:

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Demo_04 {
public static void main(String[] args) {
int[] arr = {23, 31, 214, 31, 1, 12341, 1234};
sort(arr);
toString(sort(arr));
}
//冒泡排序
public static int[] sort(int[] array){
int temp = 0;
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length-i-1; j++){
if(array[j+1] < array[j]){
temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
//打印数组
public static void toString(int[] array){
for (int i = 0; i < array.length; i++) {
if (i == 0){
System.out.print("["+array[i]);
}else if (i == array.length-1){
System.out.print(", "+array[i]+"]");
}else {
System.out.print(", "+array[i]);
}
}
}
}

稀疏数组

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

处理方式:

  • 记录数组一共有几行几列,有多少个不同值
  • 把具有不同值的元素和行列记录在一个小规模的数组中,从而缩小程序的规模
  • 选择每一个元素进行排序,然后交换其位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Demo_05 {
public static void main(String[] args) {
// 11 11 2
// 1 2 1
// 2 4 2

//转化为原始数组
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][4] = 2;
print(array1);

//获取原始数组有效值个数
int num = 0;
for (int i = 0; i < array1.length; i++) {
for (int j =0; j< array1[i].length; j++){
if (array1[i][j] != 0){
num++;
}
}
}

//创建稀疏数组
int[][] array2 = new int[num+1][3];
array2[0][0] = array1.length;
array2[0][1] = array1[0].length;
array2[0][2] = num;

int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j =0; j< array1[i].length; j++){
if (array1[i][j] != 0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}
}
}
System.out.println("========================================");
print(array2);

//还原数组
int[][] array3 = new int[array2[0][0]][array2[0][1]];
for (int i = 1; i < array2.length; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
System.out.println("========================================");
print(array3);
}

//打印数组
public static void print(int[][] array){
for (int[] ints : array) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
}
}

java面向对象

面向对象概念

面向过程思想:

  • 步骤清晰简单,第一步作什么,第二步作什么…..
  • 面对过程通常处理一些较为简单的问题

面向对象思想:

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

什么是面向对象:

  • 面向对象编程(Object-Oriented Programming.OOP)
  • 面向对象编程的本质就是:**以类的方式组织代码,以对象的形式组织(封装)数据。 **

面向对象的三大特征:

  • 封装
  • 继承
  • 多态

类和对象的关系:

  • 类是对一类事物的描述,是抽象的、概念上的定义。
  • 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。

类的语法格式

类的格式:

1
2
3
4
修饰符 class 类名{
属性声明;
方法声明;
}

属性(成员变量)默认初始化:

  • 基本数据类型:
    • 数字:0或0.0
    • char:u0000
    • boolean:false
  • 引用数据类型:null

成员属性格式:

1
修饰符 类型 属性名 = 初值;

成员方法格式:

1
2
3
修饰符 返回值类型 方法名(参数列表){
方法体语句;
}

对象的创建与使用

使用new关键字创建对象

使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及调用类中构造器

使用 对象.对象成员 的方式访问对象成员(包括属性和方法)。

pet类:

1
2
3
4
5
6
7
8
9
public class Pet {
//属性
String name;
int age;
//方法
public void say(){
System.out.println("说了一句话!");
}
}

Application类:

1
2
3
4
5
6
7
8
9
10
11
public class Application {
public static void main(String[] args) {
//通过new关键字创建对象
Pet cat = new Pet();
//通过对象去获取对象成员
cat.name = "小猫";
cat.age = 2;
System.out.println(cat.age);
cat.say();
}
}

注意:基本数据类型是值传递,除了基本数据类型其他都是引用数据类型也就是引用传递

构造器

构造器的特点:

  • 必须和类名相同
  • 没有返回值,但也不能写void
  • 不能被static、final、synchronized、abstract、native等关键字修饰,不能有return返回值语句

构造器的作用:

  • 创建对象 ,new一个对象本质在调用构造方法
  • 给对象进行初始化

根据参数不同,构造器可以分为如下两类:

  • 隐式无参构造器(系统默认提供)
  • 显式定义一个或多个有参构造器(重载)

注意:

  • Java语言中,每个类都至少有一个构造器
  • 在没有声明构造器时,类会默认生成一个无参构造
  • 默认构造器的修饰符与所属类的修饰符一致
  • 一旦显式定义了构造器,则系统不再提供默认构造器,想要使用无参构造,必须显示声明一个无参构造器
  • 一个类可以创建多个重载的构造器
  • 父类的构造器不可被子类继承,但可以通过super()关键字调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Pet {
//属性
String name;
int age;

//无参构造
public Pet() {
}

//有参构造
public Pet(String name, int age) {
this.name = name;
this.age = age;
}

//有参构造重载
public Pet(String name) {
this.name = name;
}
}

注意:this关键字是指当前类对象

访问修饰符

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写):在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法
  • private :在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public :对所有类可见。使用对象:类、接口、变量、方法
  • protected :对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

封装

我们的程序设计要追求”高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。

封装:通常,应禁止直接访问一个对象中数据的实际表现,而应通过操作接口来访问,这称为信息隐藏。(属性私有,提供公共方法get/set对属性的操作

  • 隐藏一个类中不需要对外提供的实现细节
  • 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作
  • 便于修改,增强代码的可维护性,保护数据,提高程序的安全性

Student类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Student {

//属性私有
private String name;
private int age;
private char sex;

//get方法获取
public String getName() {
return name;
}
//set方法赋值
public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
//控制用户不合法输入
if(age>120 || age<0)
this.age = 0;
else
this.age = age;
}

public char getSex() {
return sex;
}

public void setSex(char sex) {
this.sex = sex;
}
}

Application类:

1
2
3
4
5
6
7
8
9
10
11
public class Application {
public static void main(String[] args) {
Student s1 = new Student();
//通过set方法给属性设置值
s1.setName("小明");
s1.setAge(300);
//通过get方法获取属性值
System.out.println(s1.getName());
System.out.println(s1.getAge());
}
}

继承

继承的概念:

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖,组合,聚合等。

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

1
2
3
4
5
class 父类 {
}

class 子类 extends 父类 {
}

继承的特性:

  • 子类可以获取父类中声明的非 private 的属性、方法,子类不能继承父类的私有属性和方法
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以重写父类的方法。
  • Java 的继承是单继承,不可以多继承,但是可以多重继承。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
  • 所有类直接或间接继承Object类。

构造器:

  • 子类是不继承父类的构造器的,它只是调用(隐式或显式)。
  • 如果父类的构造器带有参数,则必须在子类构造器中的第一行显式地通过 super(parameter-list) 关键字调用父类的构造器并配以适当的parameter-list参数列表。
  • 如果父类构造器没有参数,系统会自动调用父类的无参构造器,子类的构造器中的第一行 super() 关键字可以省略。
  • this():本类的构造 、super():父类的构造。

super注意点:

  • super调用父类的构造方法,必须在子类构造方法的第一行
  • super只能出现在子类的方法或者构造方法中
  • super和this不能同时调用构造方法
  • super不能调用父类的私有属性和私有方法

super和this关键字的区别:

  • this关键字指向自己的引用
  • super关键字来实现对父类成员的访问
  • this关键字没有继承也可以使用
  • super关键字只能在继承条件下才可以使用

**方法的重写: **

  • 需要有继承关系,子类重写父类的方法
  • 方法名必须相同
  • 参数列表必须相同
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(JDK5 及更早版本返回类型要一样,java7 及更高版本可以不同)
  • 修饰符:范围可以扩大但不可以缩小:public>protected>default
  • 抛出的异常:范围,可以被缩小,但不能扩大
  • 父类的私有方法不能被重写,但是能够再次声明
  • 构造方法不能被重写
  • 声明为final的方法不能被重写(final修饰的方法属于常量,在常量池里面)
  • 声明为static的方法不能被重写,但是能够再次声明(static修饰的方法属于类方法,不属于实例)

为什么需要重写:父类的功能,子类不一定需要,或者不一定满足。

final关键字需要注意的地方:

  • final 关键字声明类可以把类定义为不能继承的,即最终类
  • final 关键字修饰方法,该方法不能被子类重写
  • final 关键字修饰的变量不能被修改
  • 被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final

父类Person:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {
public String name = "小红";
public int age;

//父类的有参构造
public Person(String name) {
System.out.println("Person有参构造");
}

public static void say(){
System.out.println("人说了一句话!");
}

public void sleep(){
System.out.println("人睡觉!");
}
}

子类Student:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Student extends Person{
public int id;
public String name = "小明";

public Student(String name) {
//子类必须通过super关键字显示调用父类的有参构造方法,且必须在第一行
super(name);
System.out.println("Student有参构造");
}

public void study(String name){
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}

//父类的静态方法不能被重写,但可以再次声明
public static void say(){
System.out.println("学生说了一句话!");
}

//重写父类的sleep方法
@Override
public void sleep(){
System.out.println("学生睡觉!");
}
}

main方法类Application:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Application {
public static void main(String[] args) {
Student student1 = new Student("小明");
student1.age = 2;
student1.study("小王");
student1.say();
student1.sleep();
//向上转型
Person person1 = new Student("小明");
person1.say();
student1.sleep();
}
}

多态

多态概念:

多态是同一个行为具有多个不同表现形式或形态的能力,也就是同一方法可以根据发送对象的不同而采用多种不同的行为方式。

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

多态存在的三个必要条件:

  • 继承
  • 重写
  • 父类引用指向子类对象:Person per = new Student();

注意:多态是方法的多态,属性没有多态性。

多态的优点:

  • 可替代性、可扩充性、接口性、灵活性、简化性。

转型:

  • 向上转型:父类引用指向子类对象。

  • 向下转型:子类对象指向父类引用(向下转型基于向上转型)。

父类Person:

1
2
3
4
5
6
7
8
9
public class Person {
public void run(){
System.out.println("Person->run");
}
//父类独有方法
public void sleep(){
System.out.println("Person->sleep");
}
}

子类Student:

1
2
3
4
5
6
7
8
9
10
public class Student extends Person{
//重写父类的run方法
public void run(){
System.out.println("Student->run");
}
//子类独有的方法
public void eat(){
System.out.println("Student->eat");
}
}

main方法类Application:

1
2
3
4
5
6
7
8
9
10
11
public class Application {
public static void main(String[] args) {
//向上转型:父类引用指向子类对象
Person per = new Student();
//run方法被子类重写,所以调用的是重写后的方法
per.run();
per.sleep();
//向下转型:需要向下转型才能调用子类独有的方法
((Student) per).eat();
}
}

instanceof关键字:

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例。

格式:boolean result = obj instanceof Class

其中 obj 为一个对象且必须为引用类型,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果 result 都返回 true,否则返回false(如果匹配,就可以进行向上转型)。

注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

子类Student和Teacher继承Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Application {
public static void main(String[] args) {
/*
Object > Person > Student
Object > Person
Object > Person > Teacher
Object > String
*/
Object object = new Student();
System.out.println(object instanceof Student); //true
System.out.println(object instanceof Person); //true
System.out.println(object instanceof Object); //true
System.out.println(object instanceof Teacher); //false
System.out.println(object instanceof String); //false
System.out.println("===============================");
Person person = new Student();
System.out.println(person instanceof Student); //true
System.out.println(person instanceof Person); //true
System.out.println(person instanceof Object); //true
System.out.println(person instanceof Teacher); //false
//System.out.println(person instanceof String); //编译报错
System.out.println("===============================");
Student student = new Student();
System.out.println(student instanceof Student); //true
System.out.println(student instanceof Person); //true
System.out.println(student instanceof Object); //true
//System.out.println(student instanceof Teacher); //编译报错
//System.out.println(student instanceof String); //编译报错
}
}

static关键字

static关键字:

  • 是一个修饰符,用于修饰成员变量和成员方法,也可以用于修饰内部类。
  • 不在堆空间中,不是每个对象单独有的,是所有对象公用一份,节省内存空间。
  • 当成员被static修饰后,多了一种调用方式,除了被对象调用外,还可以直接用类名调用,格式:类名.静态变量名。

特点:

  • 只要有类,就有这个静态存储变量,随着类的加载而加载,随着类的消失而消失。
  • 优先于对象存在。
  • 被所有对象共享。
  • 可以直接通过类名调用。

实例变量和类变量(静态变量)的区别:

  • 存放位置:
    • 类变量:随着类的加载而加载,存在于方法区中。
    • 成员变量:随着对象的建立,存在于堆内存中。
  • 生命周期:
    • 类变量:生命周期最长,随着类的消失而消失。
    • 成员变量:随着对象的消失而消失。

静态的使用注意事项:

  • 静态方法只能访问静态成员。
  • 非静态方法可以访问静态成员和非静态成员。
  • 静态方法不可以定义this,super关键字;因为this和super是随着对象的建立而建立的,而静态方法优先于对象,随着类的加载而加载的。

静态有利有弊:

  • 利:对对象的共享数据进行单独空间的存储,节省空间,没有必要每个对象都存储一份;可以直接被类调用。

  • 弊:生命周期过长,访问出现局限性。

什么时候使用静态数据:

  • 出现共享数据时,该数据被静态修饰。
  • 对象中的特有数据要定义成非静态的堆内存中。

**在静态方法中不能访问类的非静态成员,因为非静态成员都是须依赖具体的对象才能够被调用。 **

static可以修饰:

  • 方法
  • 成员变量
  • 代码块(不能出现在方法内部)
  • 内部类

静态代码块、匿名代码块、构造方法执行顺序:

  • Java虚拟机先将类加载进内存,会经历加载->验证->准备->解析->初始化,在初始化阶段时,JVM会先将被static修饰的代码逐一执行,所以就会先执行静态代码块,而匿名代码块在创建对象的时候就会自动执行,并且在构造器之前执行。
  • 静态代码块是类一加载就执行,永久只执行一次,JVM在加载类之前会检查该类是否被加载过,如果没有加载过,那么就对该类进行加载,同时会执行初始化静态变量、执行静态代码块,如果已经被加载了,那么就不会对该类进行加载,从而也不会初始化该类,当使用该类的时候,可以直接使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo_03 {

//类实例化的时候执行,并且在构造器之前执行
{
System.out.println("匿名代码块");
}

//只执行一次
static {
System.out.println("静态代码块");
}

//类实例化的时候执行
public Demo_03(){
System.out.println("构造方法");
}

public static void main(String[] args) {
Demo_03 demo1 = new Demo_03();
System.out.println("=====分割线======");
//每实例化一次类都会执行一次匿名代码块和构造方法
Demo_03 demo2 = new Demo_03();
}
}

静态导入包:

1
2
3
4
5
6
7
//静态导入包:可以直接导入类下的方法
import static java.lang.Math.random;
public class Demo_04 {
public static void main(String[] args) {
System.out.println(random());
}
}

抽象类

什么是抽象类:

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类:

  • abstract修饰的类叫做抽象类,抽象类不能被实例化,即不能new出来一个抽象类的对象。

  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

  • 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。

  • 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

  • 在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象方法:

  • abstract关键字修饰的方法叫做抽象方法,抽象方法必须定义在抽象类或接口中。
  • 如果一个类包含了抽象方法,那么这个类一定要声明成抽象类或接口。
  • 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
  • abstract关键字不能用来修饰私有方法、静态方法、final修饰的方法、final修饰的类。

抽象类的具体用途:

  • 抽象类的抽象方法定义一个规范,或者叫做约定,具体实现交给子类来做。
  • 提高开发效率。

抽象类可以声明并定义构造函数,因为抽象类不可以被实例化,所以构造函数只能通过构造函数链调用(Java中构造函数链指的是从其他构造函数调用一个构造函数)。

抽象类不能被实例化但构造函数的作用是:可以用来初始化抽象类内部声明的通用变量,并被各种实现使用。

抽象类Action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//abstract:抽象类,抽象类不能被实例化
public abstract class Action {

//抽象类可以定义变量
String name;

//abstract:抽象方法,只有方法名,没有方法的具体实现,起到约束作用
public abstract void test();

//抽象类可以定义构造方法
public Action(String name) {
}
}

继承抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class A extends Action {

public A(String name) {
super(name);
}

//必须重写父类的抽象方法,不然直接报错
@Override
public void test() {
System.out.println("重写父类抽象方法");
}
}

接口

接口的概念:

接口在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过实现接口的方式,从而来实现接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。

一个接口能继承另一个接口,并且可以多继承,这和类之间的继承比较相似。

接口与类的区别:

  • 接口不能用于实例化对象
  • 接口没有构造方法
  • 接口中所有方法必须都是抽象方法(JDK8中可以有静态方法和默认方法)
  • 接口中不能包含成员变量,除了static和final共同修饰的变量
  • 接口不是被类继承,而是被类实现
  • 接口和接口之间支持多继承
  • 一个类可以实现多个接口

接口与抽象类的区别:

  • 抽象类中可以定义成员方法,但是接口不能定义成员方法
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的
  • 接口中不能含有代码块和构造器,而抽象类是可以有代码块和构造器
  • 一个类只能继承一个抽象类,而一个类可以实现多个接口

接口的特性:

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法

接口的实现:

  • 类使用implements关键字实现接口,可以实现多个接口
  • 当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类

接口的作用:

  • 接口的本质是契约、标注、规范
  • 接口可以使项目分离,所有层都面向接口开发,提高开发效率
  • 接口使代码和代码之间的耦合度降低
  • 接口可以多实现,多继承,并且一个类除了实现接口之外,还可以继承其它类

接口UserService:

1
2
3
4
5
6
7
8
//interface:接口
public interface UserService{
//接口中的变量修饰符public static final是多余的,可以省略
public static final int AGE = 0;

//接口中的所有定义其实都是抽象的,public abstract是多余的,可以省略
public abstract void addUser(String name);
}

接口AdminService:

1
2
3
public interface AdminService {
void addAdmin(String name);
}

实现类UserServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//implements:实现接口,可以实现多个接口
public class UserServiceImpl implements UserService, AdminService{

//重写接口方法
@Override
public void addUser(String name) {
System.out.println("添加一条数据");
}

@Override
public void addAdmin(String name) {
System.out.println("添加一名用户");
}
}

接口匿名实现类Application:

1
2
3
4
5
6
7
8
9
10
11
public class Application {
public static void main(String[] args) {
//接口匿名实现类
new UserService(){
@Override
public void addUser(String name) {

}
};
}
}

Java 8中关于接口的改进

Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

  • 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。

    • 我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
  • 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。

    • 我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
1
2
3
4
5
6
7
8
9
10
interface CompareA{
//接口中定义静态方法
static void test1(){
System.out.println("CompareA:test1");
}
//接口中定义默认方法
default void test2(){
System.out.println("CompareA:test2");
}
}

接口中的默认方法

若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突

解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。

若一个接口中定义了一个默认方法,而实现类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class JDK8 {
public static void main(String[] args) {
CompareImpl com = new CompareImpl();
//接口中定义的静态方法只能通过接口调用
// com.test1();
CompareA.test1();
//通过实现类对象可以调用接口中的默认方法,如果实现类重写了接口中的默认方法,则调用的是重写后的方法
com.test2();

}
}

interface CompareA{
//接口中定义静态方法
static void test1(){
System.out.println("CompareA:test1");
}
//接口中定义默认方法
default void test2(){
System.out.println("CompareA:test2");
}
}

interface CompareB{
//接口中定义静态方法
static void test1(){
System.out.println("CompareB:test1");
}
//接口中定义默认方法
default void test2(){
System.out.println("CompareB:test2");
}
}

class CompareC{
public void test2(){
System.out.println("CompareC:test2");
}
}

class CompareImpl extends CompareC implements CompareA,CompareB{
public void test2(){
System.out.println("CompareImpl:test2");
}
public void test3(){
test2(); //调用自身test2方法
super.test2(); //调用父类中的test2方法
CompareA.super.test2(); //调用实现类中的test2方法
CompareB.super.test2(); //调用实现类中的test2方法
}
}

内部类

定义:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。

内部类的分类:

  • 成员内部类(静态、非静态 )
  • 局部内部类(方法内、代码块内、构造器内)

成员内部类的理解:

  • 作为外部类的成员

    • 调用外部类的结构
    • 可以被static修饰
    • 可以被四种不同的权限修饰
  • 作为一个类

    • 类内可以定义属性、方法、构造器等。
    • 可以被final修饰,表示此类不能被继承。
    • 可以被abstract修饰。

创建成员内部类对象:

  • 静态成员内部类实例化:通过实例化外部类.内部类
  • 普通成员内部类实例化:创建的外部类对象.实例化内部类

内部类中调用外部类的结构:

  • 外部类.this.变量(方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class InnerClassTest {
public static void main(String[] args) {

//静态成员内部类实例化:通过外部类.内部类实例化
Pet.Cat cat = new Pet.Cat();

//普通成员内部类实例化:创建外部类对象.内部类实例化
Pet pet = new Pet();
Pet.Dog dog = pet.new Dog();

}
}

class Pet{
String name;
int age;

public void eat(){
System.out.println("动物吃饭!");
}

//普通成员内部类
class Dog{
String name;
public void eat(){
System.out.println("小狗吃饭!");
System.out.println(this.name); //调用自身属性
System.out.println(Pet.this.name); //调用外部类属性
Pet.Cat cat = new Pet.Cat();
System.out.println(cat.name); //调用同级内部类属性
}
}

//静态成员内部类
static class Cat{
String name;
public void eat(){
System.out.println("小猫吃饭!");
}
}

public void method(){
//局部内部类:定义在方法体中
class AA{

}
}

{
//局部内部类:定义在代码块中
class BB{

}
}

public Pet(){
//局部内部类:定义在构造方法中
class CC{

}
}
}

注意点:在局部内部类的方法中(比如:show方法)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num变量)的话,要求此局部变量使用final修饰。

  • jdk 7及之前版本:要求此局部变量显式的声明为final的
  • jdk 8及之后的版本:可以省略final的声明
1
2
3
4
5
6
7
8
9
10
11
12
13
class Pet{

public void method(){
final int num = 10;
//局部内部类:定义在方法体中
class AA{
public void show(){
// num = 20;
System.out.println(num);
}
}
}
}

java异常

异常体系

在Java语言中,将程序执行中发生的不正常情况称为“异常”。

Java程序在执行过程中所发生的异常事件可分为两类:

  • Error:Java虚拟机无法解决的严重问题。
    • 如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性 的代码进行处理。
  • Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。
    • 如: 空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界。

异常的体系结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 1.异常的体系结构
* java.lang.Throwable
* |-----java.lang.Error:一般不编写针对性的代码进行处理。
* |-----java.lang.Exception:可以进行异常的处理
* |------编译时异常(checked)
* |-----IOException
* |-----FileNotFoundException
* |-----ClassNotFoundException
* |------运行时异常(unchecked,RuntimeException)
* |-----NullPointerException
* |-----ArrayIndexOutOfBoundsException
* |-----ClassCastException
* |-----NumberFormatException
* |-----InputMismatchException
* |-----ArithmeticException
*/

编译时异常和运行时异常:

  • 编译时异常:

    • 是指编译器不要求强制处置的异常。
    • 一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。
    • java.lang.RuntimeException类及它的子类都是运行时异常。
    • 执行javac.exe命名时,可能出现的异常。
  • 运行时异常:

    • 是指编译器要求必须处置的异常。
    • 即程序在运行时由于外界因素造成的一般性异常。
    • 编译器要求Java程序必须捕获或声明所有编译时异常。
    • 执行java.exe命名时,出现的异常。

常见运行时异常:

  • ClassCastException
  • ArrayIndexOutOfBoundsException
  • NullPointerException
  • ArithmeticException
  • NumberFormatException
  • InputMismatchException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package _exception;

import org.junit.Test;
import java.util.Date;
import java.util.Scanner;

public class ExceptionTest {
/*
常见运行时异常
*/
//类型转换异常
@Test
public void test1(){
Object obj=new Date();
String str=(String)obj;
}
//数组越界异常
@Test
public void test2(){
int[] arr=new int[3];
arr[3]=1;
}
//空指针异常
@Test
public void test3(){
String str=null;
str.length();
}
//算术异常
@Test
public void test4(){
int a=3;
System.out.println(a/0);
}
//数字格式化异常
@Test
public void test5(){
String str="abc";
int i = new Integer(str);
}
//输入不匹配异常
@Test
public void test6(){
Scanner sc=new Scanner(System.in);
int i = sc.nextInt();
}
}

常见的错误:

  • StackOverflowError
  • OutOfMemoryError
1
2
3
4
5
6
7
8
9
10
11
package _error;

public class ErrorTest {
public static void main(String[] args) {
//java.lang.StackOverflowError:栈溢出错误
// main(args);

//java.lang.OutOfMemoryError:堆溢出错误
int[] arry=new int[(int) Math.pow(1024,5)];
}
}

异常处理

Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

java异常处理的抓抛模型:

  • 过程一:”抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。
    • 一旦抛出对象以后,其后的代码就不再执行。
    • 关于异常对象的产生:
      • 系统自动生成的异常对象。
      • 手动的生成一个异常对象,并抛出(throw)。
  • 过程二:”抓”:可以理解为异常的处理方式:
    • try-catch-finally
    • throws

异常处理方式一:try-catch-finally

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try{
//可能出现异常的代码

}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
....
finally{
//一定会执行的代码
}

说明:

  • 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
  • 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理,一旦处理完成,就跳出当前的try-catch结构(在没写finally的情况),继续执行其后的代码。
  • catch中的异常类型如果没子父类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面,否则报错。
  • 常用的异常对象处理的方法:
    • String getMessage():获取异常简单的描述信息。
    • printStackTrace():打印异常信息。
  • 在try结构中声明的变量,出了try结构以后,就不能再被调用。
  • try-catch-finally结构可以嵌套。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package _exception;

import org.junit.Test;

public class TryCatchTest {
@Test
public void test(){
try {
String str="abc";
int i=Integer.parseInt(str);
System.out.println(i);
}catch (NumberFormatException e){
//打印异常信息
// e.printStackTrace();
System.out.println(e.getMessage());
}catch (Exception e){
System.out.println("出现异常了!!!");
}
}
}

如何看待代码中的编译时异常和运行时异常?

  • 体会1:使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
  • 体会2:开发中,由于运行时异常比较常见,所以通常就不针对运行时异常编写try-catch-finally。针对于编译时异常,必须捕获,否则编译报错。

finally关键字:

  • finally是可选择的。
  • finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中return语句,catch中return语句等情况。
  • 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package _exception;

import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FinallyTest {
@Test
public void test1(){
FileInputStream fileInputStream=null;
try {
File file = new File("hello.txt");
fileInputStream = new FileInputStream(file);
int data=fileInputStream.read();
while (data!=-1){
System.out.print((char) data);
data=fileInputStream.read();
}
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (fileInputStream!=null)
//关闭资源
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

异常处理方式二:throws 异常类型

“throws + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。

一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package _exception;

import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ThrowsTest {
@Test
public void test(){
try {
//调用该方法需要处理异常,或者也向上抛出
method();
} catch (IOException e) {
e.printStackTrace();
}
}
//throws向上抛异常,抛给了方法的调用者
public void method() throws IOException {
FileInputStream fileInputStream=null;
File file = new File("hello.txt");
fileInputStream = new FileInputStream(file);
int data=fileInputStream.read();
while (data!=-1) {
System.out.print((char) data);
data = fileInputStream.read();
}
}
}

对比两种处理方式:

  • try-catch-finally:真正的将异常给处理掉了。
  • throws的方式只是将异常抛给了方法的调用者,并没真正将异常处理掉。

体会开发中应该如何选择两种处理方式?

  • 如果父类中被重写的方法没throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中异常,必须使用try-catch-finally方式处理。
  • 主函数先后调用执行递进关系的几个方法,建议这几个方法使用throws的方式进行处理,而执行主函数可以考虑使用try-catch-finally方式进行处理。
  • 子类重写的方法抛出的异常类型不能大于父类被重写的方法抛出的异常类型。

手动抛出异常

在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类的对象。

throw和throws区别:

  • throw 表示生成异常对象并抛出,声明在方法体内。
  • throws 属于异常处理的一种方式,声明在方法的声明处。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package _exception;

import org.junit.Test;

public class ThrowTest {
@Test
public void test(){
Student stu=new Student();
try {
stu.regist(-1001);
} catch (Exception e) {
//打印异常提示信息
System.out.println(e.getMessage());
}
System.out.println(stu);
}

class Student{
private int id;
public void regist(int id)throws Exception{
if(id>0){
this.id=id;
}else{
//手动抛出异常对象
// throw new RuntimeException("您输入的数据不合法!");
throw new Exception("您输入的数据不合法!");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
}

自定义异常

前面所讲的异常,都是系统自带的,系统自己处理,但是很多时候项目会出现特有问题,而这些问题并未被Java所描述并封装成对象,所以对于这些特有的问题可以按照java的对问题封装。

思想,将特有的问题进行自定义异常封装。在Java中要想创建自定义异常,需要继承Throwable或者他的子类Exception。
自定义异常注意事项:

  • 继承于现的异常体系。
  • 提供全局常量:serialVersionUID
  • 提供重载的构造器。
  • 自定义的异常通过throw抛出。
  • 自定义异常类的名字,可以根据名字判断异常类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package _exception;

import org.junit.Test;

public class MyExceptionTest {
@Test
public void test(){
Student stu = new Student();
stu.regist(-10);
System.out.println(stu);
}
}
class Student{
private int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//手动抛出异常对象
// throw new RuntimeException("您输入的数据不合法!");
throw new MyException("您输入的数据不合法!");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
/*
自定义异常需要继承已有的异常体系,一般继承与RuntimeException或Exception
*/
class MyException extends RuntimeException{
static final long serialVersionUID = -705555555555939L;
public MyException(){}
public MyException(String msg){
super(msg);
}
}

面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package _exception;

public class Test {
public static void main(String[] args) {
int test = test(3,5);
System.out.println(test);//8
}

public static int test(int x, int y){
int result = x;
try{
if(x<0 || y<0){
return 0;
}
result = x + y;
return result;
}finally{
result = x - y;
return result;
}
}
}

面试题总结:

  • finally语句块一定会被执行,即使catch中又出现异常了,try中return语句,catch中return语句等情况。
  • try结构中的return语句会先返回对象,然后执行finally语句块,最后结束方法。
  • return既可以作为返回值,也可以用于结束方法体。