Java基础

基础

JVM JRE JDK

JVM:Java Virtual Machine
JDK:Java Development Kit(Java开发工具包)
JRE:Java Runtime Environment(Java运行环境)

java文件执行过程

  1. 源代码文件 .java
  2. 编译器编译
  3. 字节码文件 .class
  4. 解释器解释
  5. 执行

跨平台通过JVM实现

JRE包含JVM,JDK包含JRE

环境变量

JAVA_HOME 配置JDK安装路径,通常Eclipse/NetBeans/Tomcat等软件就是通过搜索JAVA_HOME变量来找到并使用安装好的jdk。
PATH 配置JDK命令文件的位置
CLASSPATH 配置类库文件的位置

Java 关键字区分大小写

标识符

标识符就是用于给 Java 程序中变量、类、方法等命名的符号。类名、变量名、方法名等都是标识符
使用标识符时,需要遵守几条规则:

  1. 标识符可以由字母、数字、下划线(_)、美元符($)组成,但不能包含 @、%、空格等其它特殊字符,不能以数字开头。
  2. 标识符不能是 Java 关键字和保留字( Java 预留的关键字,以后的升级版本中有可能作为关键字),但可以包含关键字和保留字。如:不可以使用 void 作为标识符,但是 Myvoid 可以
  3. 标识符是严格区分大小写的。 所以涅,一定要分清楚 imooc 和 IMooc 是两个不同的标识符哦!
  4. 标识符的命名最好能反映出其作用,做到见名知意。

变量

在 Java 中,我们通过三个元素描述变量:变量类型、变量名以及变量值。

  1. 变量名由多单词组成时,第一个单词的首字母小写,其后单词的首字母大写,俗称骆驼式命名法(也称驼峰命名法),如 myAge
    2.变量命名时,尽量简短且能清楚的表达变量的作用,做到见名知意。如:定义变量名 stuName 保存“学生姓名”信息


基本数据类型变量存的是数据本身,而引用类型变量存的是保存数据的空间地址
String 是一种常见的引用数据类型,用来表示字符串。
char使用两个字节,使用单引号括起来

  1. Java 中的变量需要先声明后使用
  2. 变量使用时,可以声明变量的同时进行初始化,也可以先声明后赋值
  3. 变量中每次只能赋一个值,但可以修改多次
  4. main 方法中定义的变量必须先赋值,然后才能输出
  5. 虽然语法中没有提示错误,但在实际开发中,变量名不建议使用中文,容易产生安全隐患,譬如后期跨平台操作时出现乱码等等

自动类型转换

  1. 目标类型能与源类型兼容,如 double 型兼容 int 型,但是 char 型不能兼容 int 型
  2. 目标类型大于源类型,如 double 类型长度为 8 字节, int 类型为 4 字节,因此 double 类型的变量里直接可以存放 int 类型的数据,但反过来就不可以了

强制类型转换

1
(数据类型)数值

数值上并未进行四舍五入,而是直接将小数位截断。
强制类型转换可能会造成数据的丢失

常量

它的值被设定后,在程序运行过程中不允许改变

1
final 常量名 = 值;

常量名一般使用大写字符

注释

一般来说,对于一份规范的程序源代码而言,注释应该占到源代码的 1/3 以上。

Java 中注释有三种类型:单行注释、多行注释、文档注释
文档注释以/**开头,以*/结尾
多行注释以/*开头,以/结尾
单行注释以/开头,行未结尾

通过 javadoc 命令从文档注释中提取内容,生成程序的 API 帮助文档。

1
javadoc -d doc Demo03.java

使用文档注释时还可以使用 javadoc 标记,生成更详细的文档信息:

1
2
3
4
5
6
@author 标明开发该类模块的作者
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@param 对方法中某参数的说明
@return 对方法返回值的说明
@exception 对方法可能抛出的异常进行说明

运算符

Java 语言中常用的运算符可分为如下几种:

  • 算术运算符
  • 赋值运算符
  • 比较运算符
  • 逻辑运算符
  • 条件运算符

++出现在左边,先执行自增,然后赋值
++出现在右边,先赋值,后执行自增

1
2
double ave = 24/4;
System.out.println(ave);

结果是6.0


比较的结果是一个布尔值( true 或 false )

><>=<=只支持左右两边操作数是数值类型

==!= 两边的操作数既可以是数值类型,也可以是引用类型

equals()用于判断字符串的内容是否相同,相同返回true,反之false


“短路”现象
譬如:( one > two ) && ( one < three ) 中,如果能确定左边 one > two 运行结果为 false , 则系统就认为已经没有必要执行右侧的 one < three 啦。

同理,在( one > two ) || ( one < three ) 中,如果能确定左边表达式的运行结果为 true , 则系统也同样会认为已经没有必要再进行右侧的 one < three 的执行啦!

1
布尔表达式 ? 表达式1 :表达式2

如果布尔表达式的值为 true ,则返回 表达式1 的值,否则返回 表达式2 的值

流程控制语句

if语句

1
2
3
if (score>90){
System.out.println("哟");
}
1
2
3
4
5
if (score>90){
System.out.println("哟");
}else{
System.out.println("噢");
}
1
2
3
4
5
6
7
if (score>90){
System.out.println("哟");
}else if (score >70){
System.out.println("噢");
}else{
System.out.println("喵");
}
1
2
3
4
5
6
7
8
9
if (score>90){
if (sex.equals("")){
System.out.println("哟");
}else {
System.out.println("噢");
}
}else{
System.out.println("喵");
}

switch语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int num =1;
switch(num){
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
break;
case 3:
System.out.println("3");
break;
default:
System.out.println("default");
}

执行过程:当 switch 后表达式的值和 case 语句后的值相同时,从该位置开始向下执行,直到遇到 break 语句或者 switch 语句块结束;如果没有匹配的 case 语句则执行 default 块的代码。

  • switch 后面小括号中表达式的值必须是整型或字符型
  • case 后面的值可以是常量数值,如 1、2;也可以是一个常量表达式,如 2+2 ;但不能是变量或带有变量的表达式,如 a * 2
  • case 匹配后,执行匹配块里的程序代码,如果没有遇见 break 会继续执行下一个的 case 块的内容,直到遇到 break 语句或者 switch 语句块结束
  • default 块可以出现在任意位置,也可以省略
  • 可以把功能相同的 case 语句合并起来
1
2
3
case 1:
case 2:
System.out.println("");

while

先判断,后执行

1
2
3
4
5
int i=1;
while(i <=1000){
System.out.println("嗯");
i++;
}

do…while

先执行,后判断
保证循环至少被执行一次

1
2
3
4
5
int i=1;
do{
System.out.println("哦");
i++
}while (i<=1000);

for

1
2
3
for(int i=0;i<=1000;i++){
System.out.println("咦");
}

for 关键字后面括号中的三个表达式必须用 “;” 隔开,三个表达式都可以省略,但 “;” 不能省略。

  • 省略“循环变量初始化”,可以在 for 语句之前由赋值语句进行变量初始化操作
  • 省略“循环条件”,可能会造成循环将一直执行下去,也就是我们常说的“死循环”现象
  • 省略“循环变量变化”,可以在循环体中进行循环变量的变化

for 循环变量初始化和循环变量变化部分,可以是使用 “,” 同时初始化或改变多个循环变量的值

循环条件部分可以使用逻辑运算符组合的表达式,表示复杂判断条件,但一定注意运算的优先级

break

break 语句退出指定的循环,直接执行循环后面的代码。

continue

跳过循环体中剩余的语句执行下一次循环

多重循环

java.util.Scanner类 ,输入

1
2
3
4
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
System.out.println("Please input score:");
int score = scanner.nextInt();

System.out.println 换行输出
System.out.print 不换行输出

数组

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
# 声明
int[] scores;
double height[];
# 分配空间
scores = new int[5];
height = new double[5];
int[] scores = new int[5];
# 赋值
scores[0] = 89;
scores[1] = 79;
int[] scores = {87,89};
# 造价于 int[] scores = new int[]{87,89};
# 取值
scores[1]
# 长度
scores.lenght;
# 遍历
for(int i =0;i<scores.length;i++){
System.out.println(scores[i]);
}
for(int score:scores){
System.out.println(score);
}

数组中的元素都可以通过下标来访问,下标从 0 开始。
数组下标的范围是 0 至 数组长度-1,如果越界访问,就会报错。

Arrays

Arrays 类是 Java 中提供的一个工具类,在 java.util 包中。该类中包含了一些方法用来直接操作数组,比如可直接实现数组的排序、搜索等

1
2
3
4
5
6
7
8
import java.util.Arrays;
Arrays.sort(scores);
# 正序排列
# 改变原数组
Arrays.toString(scores);
# 将数组转换为字符串,多个元素之间使用逗号和空格隔开

二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 声明
int[][] nums = new int[2][3];
# 赋值
nums[0][0] = 12;
# 取值
nums[1][1];
# 遍历
for(int i = 0; i< nums.length; i++){
for(int j =0; j<nums[i].length; j++){
System.out.println(nums[i][j]);
}
}

方法

定义方法

1
2
3
访问修饰符 返回值类型 方法名(参数列表){
方法体
}
  1. 访问修饰符:方法允许被访问的权限范围, 可以是 public、protected、private 甚至可以省略
  2. 返回值类型:方法返回值的类型,如果方法不返回任何值,则返回值类型指定为 void;如果方法具有返回值,则需要指定返回值的类型,并且在方法体中使用 return 语句返回值。方法的返回值最多只能有一个,不能返回多个值
  3. 方法名:定义的方法的名字,必须使用合法的标识符
  4. 参数列表:传递给方法的参数列表,参数可以有多个,多个参数间以逗号隔开,每个参数由参数类型和参数名组成,以空格隔开。定义方法时的参数称为形参,目的是用来定义方法需要传入的参数的个数和类型;把调用方法时的参数称为实参,是传递给方法真正被处理的值。调用带参方法时,必须保证实参的数量、类型、顺序与形参一一对应。多个参数间以逗号分隔
  • 方法体放在一对大括号中,实现特定的操作
  • 方法名主要在调用这个方法时使用,需要注意命名的规范,一般采用第一个单词首字母小写,其它单词首字母大写的形式

调用方法

1
对象名.方法名();

重载

如果同一个类中包含了两个或两个以上方法名相同、方法参数的个数、顺序或类型不同的方法,则称为方法的重载

当调用被重载的方法时, Java 会根据参数的个数和类型来判断应该调用哪个重载方法,参数完全匹配的方法将被执行。

判断方法重载的依据:

  1. 必须是在同一个类中
  2. 方法名相同
  3. 方法参数的个数、顺序或类型.
  4. 与方法的修饰符或返回值没有关系

类和对象

类是对象的类型
具有相同属性和方法的一组对象的集合

属性:对象具有的各种特征
方法:对象执行的操作

所有Java程序都以类class为组织单元

定义类的方法:

  1. 定义类名
  2. 编写类的属性
  3. 编写类的方法

对象

使用对象的步骤

  1. 创建对象类名 对象名 = new 构造方法();
  2. 使用对象对象名.属性;对象名.方法();

成员变量和局部变量

成员变量:即类的属性
局部变量:类的方法中定义的变量

成员变量和局部变量的区别

  1. 作用域不同:局部变量的作用域仅限于定义它的方法,成员变量的作用域在整个类内部都是可见的。
  2. 初始值不同:java会给成员变量一个初始值,java不会给局部变脸赋予初始值。
  3. 在同一个方法中,不允许有同名局部变量;在不同的方法中,可以有同名局部变量。
  4. 两类变量同名时,局部变量具有更高的优先级(就近原则)。

构造方法

构造方法是定义在Java类中的一个用来初始化对象的方法,构造方法与类同名且没有返回值

构造方法的语法格式:

1
2
3
public 类名(参数){
//初始化代码
}

public后面没有返回值类型,构造方法名与类名相同,括号内可以指定参数

构造方法分为无参构造方法和有参构造方法。有参构造方法的目的就是初始化对象中成员变量的值。通过无参或者有参的构造方法都可以创建对象,但是后者不但可以创建对象而且可以给对象中的实例变量赋初值

  1. 使用new+构造方法创建一个新的对象,当我们创建对象的时候,其实我们执行的是构造方法
  2. 当没有指定构造方法时,系统会自动添加无参的构造方法,可以自定义无参的构造方法
  3. 当有指定构造方法时,无论是有参无参的构造方法,都不会自动添加无参的构造方法
  4. 构造方法的重载:方法名相同,但参数不同的多个方法,调用时会自动根据不同的参数选择相应的方法
  5. 构造方法不但可以给对象的属性赋值,还可以通过条件判断保证给对象的属性赋一个合理的值

static关键字

静态变量

Java 中被 static 修饰的变量称为静态变量或类变量。它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享。静态成员可以使用类名直接访问,也可以使用对象名进行访问。

使用 static 可以修饰变量、方法和代码块。

静态成员属于整个类,当系统第一次使用该类时,就会为其分配内存空间直到该类被卸载才会进行资源回收

静态方法

使用 static 修饰方法,称为静态方法或类方法。 main 方法就是静态方法。

静态方法中可以直接调用同类中的静态成员,但不能直接调用非静态成员。
在普通成员方法中,则可以直接访问同类的非静态变量和静态变量。
静态方法中不能直接调用非静态方法,需要通过对象来访问非静态方法。

静态数据块

Java 中可以通过初始化块进行数据赋值

1
2
3
4
5
6
public class HelloWorld{
String name;
{
name = "John";
}
}

在类的声明中,可以包含多个初始化块,当创建类的实例时,就会依次执行这些代码块。如果使用 static 修饰初始化块,就称为静态初始化块。
静态初始化块只在类加载时执行,且只会执行一次,同时静态初始化块只能给静态变量赋值,不能初始化普通的成员变量。


程序运行时静态初始化块最先被执行,然后执行普通初始化块,最后才执行构造方法。

面向对象的三大特性:封装、继承、多态

封装

将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。

好处:只能通过规定的方法访问数据;隐藏类的实例细节,方便修改和实现。

封装实现方法:

  1. 修改属性可见性,设为private
  2. 创建getter\setter方法,用于属性的读写。
  3. getter\setter中加入属性控制语句(对属性值的合法性进行判断)。

packege,包名间可以用点来区别文件夹
且要放在java程序的第一行
包名间用.隔开

包的作用:
1.管理java文件
2.管理同名文件冲突
加上不同的包来区分相同文件名的类
例如:音乐类——MyClassMusic

1
2
com.imooc.music.MyClassMusic
com.imooc.movie.MyClassMusic

系统中的包
java.lang.(类) 放置java语言基础类
java.util.(类)放置java语言工具类
java.io.(类)包含输入输出相关功能的类

包的使用

  1. 可以使用import关键字,在某个文件使用其他文件中的类。import com.imooc.music.MyClass
  2. java中,包的命名规范是全小写字母拼写
  3. 使用的时候不但可以加载某个包下的所有文件com.imooc.*。也可以加载某个具体子包下的所有文件com.imooc.music.*

访问修饰符

修饰属性和方法的访问范围

this关键字

this关键字代表当前对象
this.属性 操作当前对象的属性
this.方法 调用当前对象的方法

封装对象的属性的时候,经常会使用this关键字

内部类

内部类( Inner Class )就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类。

内部类作用:

  1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
  2. 内部类的方法可以直接访问外部类的所有数据,包括私有的数据
  3. 内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便

内部类分类

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

成员内部类

内部类中最常见的就是成员内部类,也称为普通内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Outer{
private int a = 99;
public class Inner{
int b = 2;
public void test(){
System.out.println("外部类中的变量a:"+a);
System.out.println("内部类中的变量b:"+b);
}
}
public static void main(String[] args){
Outer outer = new Outer();
Inner i = outer.new Inner();
i.test();
}
}
  1. Inner 类定义在 Outer 类的内部,相当于 Outer 类的一个成员变量的位置,Inner 类可以使用任意访问控制符,如 publicprotectedprivate 等。
  2. Inner 类中定义的 test() 方法可以直接访问 Outer 类中的数据,而不受访问控制符的影响,如直接访问 Outer 类中的私有属性a。
  3. 定义了成员内部类后,必须使用外部类对象来创建内部类对象,而不能直接去 new 一个内部类对象,即:内部类 对象名 = 外部类对象.new 内部类( );
  4. 编译上面的程序后,会发现产生了两个 .class 文件,成员内部类的 .class 文件总是这样命名:外部类名$内部类名.class

外部类是不能直接使用内部类的成员和方法

如果外部类和内部类具有相同的成员变量或方法,内部类默认访问自己的成员变量或方法,如果要访问外部类的成员变量,可以使用 this 关键字(名字不相同的变量或方法可以直接访问)。

静态内部类

静态内部类是 static 修饰的内部类,这种内部类的特点是:

  1. 静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问
  2. 如果外部类的静态成员与内部类的成员名称相同,可通过类名.静态成员访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过成员名直接调用外部类的静态成员
  3. 创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名= new 内部类();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SOuter{
private int a = 99;
static int b = 1;
int c = 3;
public static class SInner{
int b = 2;
public void test(){
System.out.println("访问外部类的私有成员a:"+new SOuter().a);
System.out.println("访问外部类的静态成员b:"+SOuter.b);
System.out.println("访问外部类的非静态成员c:"+ new SOuter().c);
}
}
public static void main(String[] args){
SInner si = new SInner();
si.test();
}
}

方法内部类

方法内部类就是内部类定义在外部类的方法中,方法内部类只在该方法的内部可见,即只在该方法内可以使用。
由于方法内部类不能在外部类的方法以外的地方使用,因此方法内部类不能使用访问控制符和 static 修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MOuter{
public void show(){
final int a = 25;
int b = 13;
class MInner{
int c = 2;
public void print(){
System.out.println("访问外部类的方法中的常量a:"+a);
System.out.println("访问外部类中的变量b:"+b);
System.out.println("访问内部类中的变量c:"+c);
}
}
MInner mi = new MInner();
mi.print();
}
public static void main(String[] args){
MOuter mo = new MOuter();
mo.show();
}
}

继承

继承是类与类的一种关系;Java中的继承是单继承,只有一个父类。

继承的好处:

  1. 子类拥有父类的所有属性和方法。(private修饰的属性和方法无法继承)
  2. 代码可复用

语法规则

1
2
3
class 子类 extends 父类{
……
}

方法的重写

如果子类对继承父类的方法不满意,是可以重写父类继承的方法的,当调用方法时会优先调用子类的方法。

语法规则

  1. 返回值类型
  2. 方法名
  3. 参数类型及个数
    都要与父类继承的方法相同,才叫方法的重写。

重写方法的规则:

  1. 参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。
  2. 返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。如果在子类中写了一个只有返回类型不同的函数,将不是重写,且会报错
  3. 访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
  4. 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。例如:父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。

重载的规则:

  1. 必须具有不同的参数列表。
  2. 可以有不责骂的返回类型,只要参数列表不同就可以了。
  3. 可以有不同的访问修饰符。
  4. 可以抛出不同的异常。

重写与重载的区别在于:

  1. 重写多态性起作用,对调用被重载过的方法可以大大减少代码的输入量,同一个方法名只要往里面传递不同的参数就可以拥有不同的功能或返回值。
  2. 用好重写和重载可以设计一个结构清晰而简洁的类,可以说重写和重载在编写代码过程中的作用非同一般.
  3. 重写是除了函数体其他必须完全相同,如果参数不同则是重载,如果参数相同而返回类型不同则会报错

继承的初始化顺序

  1. 初始化父类再初始化子类。
    2.先执行初始化对象中属性,再执行构造方法中的初始化。

综合以上两点,顺序为:
父类属性初始化—》父类构造方法—》子类属性初始化—》子类构造方法。

在一个不存在继承的类中:初始化static变量—》执行static初始化快—》初始化普通成员变量(如果有赋值语句)—》执行普通初始化块—》构造方法

在一个存在继承的类中:初始化父类static成员变量—》运行父类static初始化块—》初始化子类static成员变量—》运行子类static初始化块—》初始化父类实例成员变量(如果有赋值语句)—》执行父类普通初始化块—》父类构造方法—》初始化子类实例成员变量(如果有赋值语句)及普通初始化块—》子类构造方法。

注意:其中变量初始化(赋值)和初始化块的执行与相关语句在源码中的放置顺序一致,不过变量声明会最先执行

final关键字

final关键字表示“最终的”,即不可修改。

final可以修饰类、方法、属性和变量

  1. 修饰类:不允许被继承
  2. 修饰方法:不允许被重写
  3. 修饰属性:则该属性不会进行隐式初始化(不会自动初始化),需要手动初始化或者在构造方法中初始化 (但二者只能选一,即只能初始化一次后便不能更改)
  4. 修饰变量,只能在声明的时候赋一次值,成为常量。 (static final 会使其成为全局常量)

super关键字

super关键字在对象内部使用,代表父类对象

  1. 访问父类的属性super.age
  2. 访问父类方法super.eat();
  3. 调用父类的构造方法super([参数]);

调用父类的构造方法需要注意:
1.子类的构造过程中必须调用其父类的构造方法
2.如果子类的构造方法没有显式调用父类的构造方法,则系统会默认调用父类的无参构造方法。
3.如果显示的调用构造方法,必须在子类的构造方法的第一行,super()。
4.如果子类的构造方法中既没有显示调用父类的构造方法,父类又没有无参的构造方法,则编译就会报错

Object类

Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类默认继承Object类,且Object类中的方法适用于所有子类

toString()方法

在Object类中的toString()方法返回对象的哈希code码(对象地址字符串)

如果我们在代码里面直接输出一个对象System.out.println(new TelePhone);,会输出这个对象在内存中的地址,我们把它称为哈希码,哈希码是通过哈希算法生成的一个字符串用来唯一区分对象的

可以通过重写toString()方法表示出对象的属性

getClass()方法

getClass()可以得到一个类对象,当我们new的时候得到的是一个类的对象,如果对象调用getClass()方法我们得到的是类对象,类对象描述的是类的代码信息(类的代码信息:这个类有哪些属性\它是什么类型\变量名是什么\它有哪些方法\方法名是什么\方法里面的代码是什么;而类的对象关注的是对象的属性值的信息或者说这个对象的数据信息)

equals方法

Object类中的equals()用于比较对象的引用是否指向同一块内存地址(对象实例化时,即给对象分配内存空间)

在比较引用对象是否相同时,==比较对象是否相等和equals一样于比较对象的引用是否指向同一块内存地址(对象实例化时,即给对象分配内存空间)

在比较引用对象是否相同时,==比较对象是否相等和equals一样

使用方法如:dog.equals(dog2);

如果是两个对象,但想判断两个对象的属性是否相同,则重写equals()方法(ecplise和Intellij都提供了自动生成重写equals方法的功能,equals() and hascode()

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean equals(Object o) {
//地址是否相同,即是否为同一对象
if (this == o) return true;
//对象是否为空,getClass()可以得到类对象,判断类的属性是否一样
if (o == null || getClass() != o.getClass()) return false;
Telephone telephone = (Telephone) o;
return Float.compare(telephone.cpu, cpu) == 0 &&
Float.compare(telephone.screen, screen) == 0 &&
Float.compare(telephone.mem, mem) == 0;
}

多态

多态指的是对象的多种形态
主要分为引用多态、方法多态
继承是多态的实现基础

引用多态

  1. 父类引用可以指向本类对象 Animal obj1 = new Animal();
  2. 父类引用可以指向子类对象 Animal obj2 = new Dog();
    但是我们不能用子类的引用指向父类对象 Dog obj3 = new Animal();//错

方法多态

  1. 父类创建本类对象时,调用的方法为本类方法
  2. 父类创建子类对象时候,调用的方法为子类重写的方法或者父类的方法

不能父类引用调用子类自己的方法

1
2
3
4
5
6
7
8
9
Animal obj1 = new Animal();
Animal obj2 = new Dog();
Animal obj3 = new Cat();
obj1.eat();//Animal的方法
obj2.eat();//Dog重写的方法
obj3.eat();//Cat没有重写,使用继承自Animal中的方法
obj2.watchDoor();//不能父类引用调用子类自己的方法

引用类型转换

  1. 向上类型转换(隐式/自动类型转换),小类型到大类型的转换
  2. 向下类型转换(强制类型转换),大类型到小类型的转换(存在风险,溢出)
  3. instanceof运算符,用来解决引用对象的类型,避免类型转换的安全性问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dog dog=new Dog();
Animal animal=dog;//正确,自动类型提升,向上类型转换
Dog dog1=(Dog)animal;//向下类型转换
Cat cat=(Cat)animal;
/*编译时不会出错(按Cat类型进行编译),但运行时会报错,
因为它开辟的是Dog类型的空间,而(无法将引用类型进行转换)
无法将dog对象转换成Cat类型,并且此方法对程序的安全性有影响。
此时应该利用instanceof和if语句结合使用,进行验证,
以保证程序的安全性
*/
if(animal instanceof Cat){
//判断animal类中是否包含Cat类型的元素,若包含则进行转换,instanceof返回值为布尔类型
Cat cat=(Cat)animal;
}else{
System.out.println("无法进行类型转换");
}

抽象类

抽象类钱使用abstract关键字修饰,则该类为抽象类。

应用场景
  1. 在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法
  2. 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性
作用

限制规定子类必须实现某些方法,但不关注实现细节

使用规则
  1. abstract定义抽象类
  2. abstract定义抽象方法,只有声明,不需要实现,没有方法体
  3. 包含抽象方法的类是抽象类
  4. 抽象类中可以包含普通的方法,也可以没有抽象方法
  5. 抽象类不能直接创建,可以使用引用变量
  6. 只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含有其他方法。
1
2
3
4
public abstract class Shape {
public abstract float circuit();//周长
public abstract float area();//面积
}

接口

定义

接口可以理解为一种特殊的类,由全局常量公共的抽象方法所组成。

类是一种具体实现体,而接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供某些方法。

语法

和类定义不同,定义接口不再使用class关键字,而是使用interface关键字。

1
2
3
4
5
[修饰符] [abstract] interface 接口名 [extends 父接口1,父接口2...]
{
零个到多个常量定义...
零个到多个抽象方法的定义...(抽象方法的abstract会自动加)
}

接口就是用来被继承、被实现的,修饰符一般建议用public,注意:不能使用private和protected修饰接口
接口中的属性是常量,即使定义时不添加public static final修饰符,系统也会自动加上
接口中的方法只能是抽象方法,即使定义时不添加public abstract修饰符,系统也会自动加上

一个类可以实现一个或多和接口,实现接口使用implements关键字。java中一个类只能继承一个父类,可以通过实现多个接口作补充

继承父类实现接口的语法:

1
2
3
[修饰符] class 类名 extends 父类 implements 接口1,接口2……{
类体部分//如果继承的是抽象类,需要实现继承的抽象方法;要实现接口中的抽象方法
}

如果要继承父类,继承父类必须在实现接口之前
接口命名时通常首字母为I,以区分类名

实现

实现时可以利用接口的引用指向实现了接口的对象,调用其方法

1
2
3
4
IPlayGame ip1 = new Psp();
IPlayGame ip2 = new SmartPhone();
ip1.playGame();
//Psp和SmartPhone都是实现了IPlayGame接口的playGame方法

配合匿名内部类使用

接口的使用还经常与匿名内部类配合
匿名内部类就是没有名字的内部类,多用于关注实现而不关注实现类的名称

语法格式:

1
2
3
4
5
6
IPlayGame ip=new IPlayGame(){
public void playGame(){
System.out.print("匿名内部类实现接口的方式……");
}
};
ip.playGame();

即通过创建接口的对象,直接写出实现的方法,再调用此方法
注意结尾的逗号

还可以直接创建并调用方法,如:

1
2
3
4
5
new IPlayGame(){
public void playGame(){
System.out.println("……");
}
}.playGame();

项目分析

  1. 数据模型分析
  2. 业务模型分析
  3. 显示和流程分析

数据模型分析

将现实世界的事与物的主要特征 –分析–抽象–> 信息系统数据存取的数据结构以及约束信息,数据结构的组成:操作(方法)、属性

业务模型分析

在设计应用程序前,应该明确该应用必须执行哪些任务。

  • 分析业务需求量是应用程序开发中最重要的步骤之一。
  • 确认业务需求的目的在于创建一个能同时满足零售商和消费者需要的解决方案。

显示和流程分析

  1. 显示:用户可以看到的信息提示界面
  2. 流程:显示信息的执行过程和步骤

异常

阻止当前方法或作用域正常运行

异常处理的作用

Java异常体系结构

所有异常都继承于Throwable类,其下有两大子类:

  1. Error类:错误,如虚拟机错误(VirtualMachineError)、线程死锁(ThreadDeath)。会使程序崩溃
  2. Exception类:异常,编码、环境、用户输入等问题

Exception类其子类主要有:

  • 非检查异常(运行时异常RuntimeException),由java虚拟机自动捕获
  • 检查异常CheckException,需要手动添加捕获和处理语句,包括文件异常IOException、SQL异常SQLException等

捕获异常

try-catch(多catch块)-finally

  1. try块:负责捕获异常,一旦try中发现异常,程序的控制权将被移交给catch块中的异常处理程序。【try语句块不可以独立存在,必须与 catch 或者 finally 块同存】
  2. catch块:如何处理?比如发出警告:提示、检查配置、网络连接,记录错误等。执行完catch块之后程序跳出catch块,继续执行后面的代码。
    ·编写catch块的注意事项:多个catch块处理的异常类,要按照先catch子类后catch父类的处理方式,因为会【就近处理】异常(由上自下)。
  3. finally:最终执行的代码,用于关闭和释放资源等,无论是否出现异常,finally块中代码都会执行


多重异常处理代码块顺序问题:先子类再父类(顺序不对也会提醒错误),finally语句块处理最终将要执行的代码

当try和catch中有return时,finally仍然会执行,顺序如下
1)有错误情况下
如果finally块中有return语句,try语句 -> catch -> finally -> finally.return
如果finally块中没有return语句,try语句 -> catch -> finally -> catch.return
2)无错误情况下
如果finally{}块中有return语句,try语句 -> finally -> finally.return
如果finally{}块中没有return语句,try语句 -> finally -> try.return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TryCatchTest {
public static void main(String[] args){
TryCatchTest tct = new TryCatchTest();
int x = tct.test();
System.out.println(x);
}
public int test(){
try{
System.out.println("try");
throw new Exception("xxx");
//return 999;
}catch (Exception e){
System.out.println("catch");
return -1;
//-1为抛出异常的习惯写法
}
finally {
System.out.println("finally");
}
}
}

如果方法中try,catch,finally中没有return语句,则会调用这三个语句块之外的return结果
e.printStackTrace()可以输出异常信息

抛出异常

1.throws的异常列表可以是抛出一条异常,也可以是抛出多条异常,每个类型的异常中间用逗号隔开
2.方法体中调用会抛出异常的方法或者是先抛出一个异常:用throw new Exception()throw写在方法体里,表示“抛出异常”这个动作
3.如果某个方法调用了抛出异常的方法,那么必须添加try... catch语句去尝试捕获这种异常,或者添加声明,将异常抛出给更上一层的调用者进行处理

1
2
3
4
public void 方法名(参数列表)throws 异常列表{
//调用会抛出异常的方法或者:
throw new Exception();
}

自定义异常

当实际应用中需要用到Java中没有的异常,就需要自定义异常
自定义异常需要继承于Java类库中意思相近的异常,或者直接继承Exception

1
class 自定义异常类 extends 异常类型{}

1
2
3
4
public class DrunkException extends Exception {
public DrunkException(){}
public DrunkException(String message){super(message);}
}

异常链

异常链指将捕获的异常包装进一个新的异常中,在新异常里添加原始的异常,并重新抛出的异常处理方式。原异常被保存为新异常的一个属性(比如cause)。这个想法是指一个方法应该抛出定义在相同的抽象层次上的异常,但不会丢弃更低层次的信息。

一个方法应该抛出定义在相同的抽象层次上的异常,(将所有捕获到的异常包装为新的异常类,即定义在相同的抽象层次上抛出)但不会丢弃更低层次的信息。

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
public class chainTest {
/**
* @param args
* Test1抛出喝大了异常
* Test2调用test1捕获了喝大了异常,并且包装成运行时异常,继续抛出
* main方法中调用test2尝试捕获test2方法抛出的异常
*/
public static void main(String[] args) {
try{
chainTest ct=new chainTest();
ct.Test2();}
catch(Exception e){
e.printStackTrace();
}
}
public void Test1()throws DrunkException{
throw new DrunkException("喝车别开酒");
}
public void Test2(){
try{
Test1();
}catch( DrunkException e){
RuntimeException rte=new RuntimeException(e);
//运行时异常,是所有java虚拟机正常操作期间可以被抛出异常的父类
//rte.initCause(e);
//一种对异常的一种包装技巧。initCause()的作用是保存原始的异常,当想要知道底层发生了什么异常的时候调用getCause()就能获得原始异常。
e.printStackTrace();
throw rte;
}
}
}

  1. 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
  2. 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
  3. 对于不确定的代码,也可以加上try-catch,处理潜在的异常
  4. 尽量去处理异常,切记只是简单的调用printStackTrace()去打印
  5. 具体如何处理异常,要根据不同的业务需求和异常类型去决定
  6. 尽量添加finally语句块去释放占用的资源

字符串

String

字符串被作为 String 类型的对象处理。 String 类位于 java.lang 包中。默认情况下,该包被自动导入所有的程序。

1
2
3
4
String s1 = "jj";
//s1存放了到字符串对象的引用
String s2 = new String();
String s3 = new String("jj");

String 对象创建后则不能被修改,是不可变的,所谓的修改其实是创建了新的对象,所指向的内存空间不同。

一旦一个字符串在内存中创建,则这个字符串将不可改变。如果需要一个可以改变的字符串,我们可以使用StringBuffer或者StringBuilder

每次 new 一个字符串就是产生一个新的对象,即便两个字符串的内容相同,使用 ==比较时也为 ”false” ,如果只需比较内容是否相同,应使用 equals()方法

  • 字符串中字符的索引从0开始,范围为 0 到 str.length()-1
  • 使用 indexOf 进行字符或字符串查找时,如果匹配返回位置索引;如果没有匹配结果,返回 -1
  • 使用 substring(beginIndex , endIndex) 进行字符串截取时,包括 beginIndex 位置的字符,不包括 endIndex 位置的字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String str = "学习 JAVA 编程";
System.out.println("字符串长度:"+str.length());
char c = '编';
System.out.println("字符串‘编’的位置:"+str.indexOf(c));
System.out.println("子字符串Java的位置:"+str.indexOf("JAVA"));
System.out.println("子字符串imooc的位置:"+str.indexOf("java"));
String[] arr = str.split(" ");
System.out.println("按空格拆分成数组:"+Arrays.toString(arr));
System.out.println("获取位置[3,7)之间的子字符串:"+str.substring(3,7));
System.out.println("转换为小写:"+str.toLowerCase());
System.out.println("获取索引位置为3的字符:"+str.charAt(3));
System.out.print("将字符串转换为byte数组:");
byte[] bytes = str.getBytes();
for(int i=0;i<bytes.length;i++){
System.out.print("bytes"+i+":"+bytes[i] + " ");
}
1
2
3
4
5
6
7
8
9
字符串长度:10
字符串‘编’的位置:8
子字符串Java的位置:3
子字符串imooc的位置:-1
按空格拆分成数组:[学习, JAVA, 编程]
获取位置[3,7)之间的子字符串:JAVA
转换为小写:学习 java 编程
获取索引位置为3的字符:J
将字符串转换为byte数组:bytes0:-27 bytes1:-83 bytes2:-90 bytes3:-28 bytes4:-71 bytes5:-96 bytes6:32 bytes7:74 bytes8:65 bytes9:86 bytes10:65 bytes11:32 bytes12:-25 bytes13:-68 bytes14:-106 bytes15:-25 bytes16:-88 bytes17:-117

1 个字节等于 8 位, gbk 编码中 1 个汉字字符存储需要 2 个字节,1 个英文字符存储需要 1 个字节。字对应的字节值为负数,原因在于每个字节是 8 位,最大值不能超过 127,而汉字转换为字节后超过 127,如果超过就会溢出,以负数的形式显示。

==” 和 equals() 有什么区别:

  • ==: 判断两个字符串在内存中首地址是否相同,即判断是否是同一个字符串对象
  • equals(): 比较存储在两个字符串对象中的内容是否一致

比较字符用==

StringBuilder类

String 类具有是不可变性,多个字符串进行拼接了以后产生一个新的临时变量并指向一个新的对象或新的地址。使用 StringBuilder 或 StringBuffer 就可以避免这个问题。

StringBuilder 和StringBuffer ,它们基本相似,不同之处,StringBuffer 是线程安全的,而 StringBuilder 则没有实现线程安全功能,所以性能略高。因此一般情况下,如果需要创建一个内容可变的字符串对象,应优先考虑使用 StringBuilder 类。

1
2
StringBuilder str1 = new StringBuilder();
StringBuilder str2 = new StringBuilder("imooc");

在需要频繁对字符串进行修改操作时使用 StringBuilder 的效率比 String 要高

常用类

包装类

为了让基本数据类型也具备对象的特性, Java 为每个基本数据类型都提供了一个包装类,这样我们就可以像操作对象那样来操作基本数据类型。

基本类型 对应的包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

包装类主要提供了两大类方法:

  1. 将本类型和其他基本类型进行转换的方法
  2. 将字符串和本类型及包装类互相转换的方法

Integer类

1
2
3
int i = 2;
Integer m = new Integer(8);
Integer n = new Integer("8");

常用方法

| 返回值 | 方法名 | 解释 |
| byte | byteValue() | 将该Integer转为byte类型 |
| double | doubleValue() | 转为double类型 |
| float | floatValue | 转为float类型 |
| int | intValue() | 转为int类型 |
| long | longValue() | 转为long类型 |
| static int | parseInt(String s) | 将字符串转换为int类型 |
| String | toString | 转为字符串类型 |
| static Integer | valueOf(String s) | 将字符串转换为Integer类型 |

基本类型和包装类之间的转换

装箱:把基本类型转换成包装类,使其具有对象的性质,又可分为手动装箱和自动装箱

1
2
3
int i = 10
Integer x = new Integer(i);//手动装箱
Integer y = i;//自动装箱

拆箱:和装箱相反,把包装类对象转换成基本类型的值,又可分为手动拆箱和自动拆箱

1
2
3
Integer j = new Integer(8);
int m = j.intValue(); //手动拆箱
int n = j; //自动拆箱

基本类型和字符串之间的转换

基本类型转换为字符串有三种方法:

  1. 使用包装类的 toString() 方法
  2. 使用String类的 valueOf() 方法
  3. 用一个空字符串加上基本类型,得到的就是基本类型数据对应的字符串
    1
    2
    3
    4
    int c = 10;
    String str1 = Integer.toString(c);
    String str2 = String.valueOf(c);
    String str3 = c + "";

将字符串转换成基本类型有两种方法:

  1. 调用包装类的 parseXxx 静态方法
  2. 调用包装类的 valueOf() 方法转换为基本类型的包装类,会自动拆箱
1
2
3
String str = "8";
int a = Integer.parseInt(str);
int b = Integer.valueOf(str);

Date 类

java.util 包中

1
2
3
4
Date d = new Date();
System.out.println(d);
// 默认输出当前时间
// Thu Jul 05 11:39:09 CST 2018

java.text 包中的 SimpleDateFormat 类可以对日期时间进行格式化

1
2
3
4
5
6
7
8
9
10
11
//使用 format() 方法将日期转换为指定格式的文本
Date d1 = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String today = sdf.format(d1);
System.out.println(today);
//使用 parse() 方法将文本转换为日期
String day = "2014年02月14日 10:30:25";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date date = sdf1.parse(day);
System.out.println("当前时间"+date);

调用 SimpleDateFormat 对象的 parse() 方法时可能会出现转换异常,即 ParseException ,因此需要进行异常处理

使用 Date 类时需要导入 java.util 包,使用 SimpleDateFormat 时需要导入 java.text 包

Calendar 类

Date 类最主要的作用就是获得当前时间,同时这个类里面也具有设置时间以及一些其他的功能,但是由于本身设计的问题,这些方法却遭到众多批评,不建议使用,更推荐使用 Calendar 类进行时间和日期的处理。

java.util.Calendar类是一个抽象类,可以通过调用 getInstance() 静态方法获取一个 Calendar 对象,此对象已由当前日期时间初始化,即默认代表当前时间,如 Calendar c = Calendar.getInstance();

1
2
3
4
5
6
7
8
9
10
11
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1; //0表示1月份
int day = c.get(Calendar.DAY_OF_MONTH);
int hour = c.get(Calendar.HOUR);
int minute = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
System.out.println(year+"年"+month+"月"+day+"日 "+hour+":"+minute+":"+second);
Date d = c.getTime(); //Calendar 和 Date 的转换
Long time = c.getTimeInMillis(); //获取此 Calendar 的时间值,以毫秒为单位

Math 类

Math 类位于 java.lang 包中,包含用于执行基本数学运算的方法, Math 类的所有方法都是静态方法,所以使用该类中的方法时,可以直接使用类名.方法名

不需要导入包

1
2
int y = (int)(Math.random()*99);
# 产生[0,99)之间的随机整数