Java基础的笔记
Hello World
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello world");
}
}
运行机制
tips
- java的代码.java可以转换为字节码文件**.class**,在不同的JVM上来运行,适用于各个系统。
- java代码写到.java文件中,通过javac.exe对该java文件编译为.class文件,再通过java.exe运行.class文件,编辑器一键实现
- Java 中的变量需要先声明后使用,所谓声明就是给他赋类型,一定要赋予类型!!!(好坑啊…)
- 在一个java源文件可以声明多个class,但是只能最多有一个类声明为public,只能加到和文件名相同的类上
- 用final定义常量名,一般使用大写字符,char类型不能用来定义常量,得用String
- 每行命令后面要加分号(这么看python语法好方便啊…)
- System.out.println(“a:”+a);通过加号来连接
- 编译以后,会生成一个或多个字节码文件,有几个class就有几个字节码文件
- 2进制用0b或0B开头,8进制用0开头,16进制用0x开头
- 应用程序 = 算法 + 数据结构
注释
Java 中注释有三种类型:单行注释、多行注释、文档注释
单行注释
//内容
多行注释,不可以多行使用
/*
内容
*/
文档注释
注释内容可以被JDK提供的工具javadoc所解析,生成一套以网页文件形式体现该程序的说明文档
/**
@author 指定java程序的作者
@version 指定源文件的版本
*/
数据类型
类型 | 占用储存空间 | 备注 |
---|---|---|
byte | 1字节 | -128~127 |
short | 2字节 | |
int | 4字节 | 默认使用 |
long | 8字节 | long型常量后面要加‘l’或‘L’ |
float | 4字节 | 精确到7位有效数字,声明时要加‘f’或‘F’ |
double | 8字节 | 默认使用 |
char | 2字节 | 1字符=2字节,使用一对’ ',可用做转义字符 |
boolean | 只能取true或false |
- 自动类型提升
- 当容量小的数据类型的变量和容量大的数据类型的变量做运算时,结果自动提升位容量大的数据类型
- byte、char、short --> int --> long --> float --> double,当byte、char、short三种变量做运算时,结果为int型
- 强制类型转换
- 需要使用强转符:()
- 强制类型转换,可能跟会导致精度损失(将小数位截断,不是四舍五入)
- 声明String类型变量时,使用一对双引号"",可以和8种基本类型变量做运算,但只能是连接运算(+),看运算顺序,String在前面全部转换为字符串,不能简单的在强制(int)回去
运算符
算数运算符
/是除法;%是取余,结果的符号与被模数的符号相同;++/–在左边先运算后操作,在右边先操作后运算 ,不会改变变量的数据类型,+=,-=等等的比较运算符也不会改变数据类型
逻辑运算符
&逻辑与;&&短路与;|逻辑或; ||短路或; !非; ^异或
逻辑与/或要两者都执行,短路与/或当第一个能判断出来时,第二个便不再执行
条件运算符
条件运算符( ? : )也称为 “三元运算符”,可以嵌套使用
语法形式:布尔表达式 ? 表达式1 :表达式2
如果布尔表达式为true,返回1的值,否则返回2的值
输入
String 变量 = new.Scanner.(System.in).next(); // 输入字符串
int 变量 = new.Scanner.(System.in).nextInt(); // 输入数字
带标签的break和continue
// 在循环关键词前面加上标签名,便可以直接break或者continue此循环,否则默认最近的循环
public class Break {
public static void main(String[] args) {
lable:for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
if (j % 5 == 0) {
System.out.println(j);
break lable;
}
}
}
}
}
数组
一维数组
- 数组本身是引用数据类型,数组的元素可以是基本数据类型和引用数据类型
- 数组的长度一旦确定便不能更改
- 执行arr1 = arr2的操作时,只是将地址传递,并没有真的复制
//声明数组
//静态初始化
int[] score; or int score[];
//分配空间,指定数组能有多少空间
//数组是引用类型数据,实例化必须要使用new
score = new int[]{80, 50, 70, 95, 84}
//动态初始化
//或者声明的时候直接分配空间
int score[] = new int[5];
//获取数组长度
数组名.length
一维数组元素默认初始化值
数组元素类型 | 默认初始化值 |
---|---|
整形 | 0 |
浮点型 | 0.0 |
char型 | 0而不是’0’,0代表null |
boolean型 | false(实际上0代表false) |
引用类型变量 | null |
一维数组的内存解析
- 栈(stack):存放局部变量
- 堆(heap):new出来的结构,有对象、数组等,
- 堆空间的每个结构都有个首地址值,通常用16进制来表示,将这个地址值赋给栈中的变量名
- 堆空间先赋上元素的默认初始化值,在进行真正的赋值
二维数组
// 静态初始化
int[][] arr = new int[][]{{1, 2, 3}, {2, 4}}; // 跟python的list一样,每个一维数组可以不一样大
//动态初始化
int[][] arr1 = new int[3][2];
int[][] arr2 = new int[3][];//一维数组里面的数组大小不定的,使用前必须要先赋值
//获取数组长度
使用.length只能得到外面一维数组的长度
二维数组元素默认初始化值
int[][] arr = new int[3][2];
System.out.println(arr[0]);
// 输出的是内层第一个一维数组的地址值
// output:[I@1b6d3586,表示int型的一维数组(有一个[)
//如果使用第二种动态定义的方法
int[][] arr = new int[3][];
System.out.println(arr[0]);
// output:null,因为内层的一维数组还没有被定义,而一维数组是引用类型,元素默认参数是null,所以output为null
System.out.println(arr[0][0]);
// 报错,空指针异常
二维数组的内存解析
Arrays工具类的使用
java.util.Arrays类是操作数组的工具类,包括操作数组的各种方法
命令 | 作用 |
---|---|
boolean equals(int[] a, int[] b); | 判断来个数组是否相等 |
String toString(int[] a); | 输出数组信息 |
void fill(int[] a, int val) | 将指定值全部替换到数组当中 |
void sort(int[] a) | 对数组进行排序 |
int binarySearch(int[] a, int key) | 数组a必须是排好序的,找到key的索引,如果没有返回负数 |
练习:对象数组
public class StudentTest{
public static void main(String[] args){
// 声明了Student类型的数组
// 跟声明String类型的数组一样的!
Student[] stus = new Student[20];
for(int i = 0;i < stus.length;i++){
stus[i] = new Student();
//对每个成员变量进行赋值
stus[i].number = i + 1;
// Math.random()的范围是[0, 1)
stus[i].state = (int)(Math.random() * 6 + 1);
stus[i].score = (int)(Math.random() * 101);
}
}
}
class Student{
int number; // 学号
int state; // 年级[1-6]
int score; //分数[0-100]
}
面向对象
主线
- Java类和类的成员:属性、方法、构造器;代码块,内部类
- 三大特征:封装性、继承性、多态性、(抽象性)
- 其他关键字:this、super、static、final、abstract、interface、package、import等
Java类和成员
- 类是抽象的概念,是对象的模板
- 对象是具体的事物,是类的具体实例
- main函数要写在class中(好奇怪的说…)
类、对象的命名规范:
- 类名首字母要大写,如果多个单词组成的类名,每个单词的首字母都要大写,不要以数字开头,不包含特殊字符
- 对象首字母小写,后面单词首字母要大写,不要以数字开头,不包含特殊字符
实例化:
Dog doudou = new Dog();
匿名对象
// 不给new出来的对象赋予形参
new Phone().price = 1999;
// 只能用一次
// 但一般被传进别的函数,也是被赋予了形参
属性
也称为成员变量、field、域、字段,(可以在外面赋值…)
属性(成员变量) VS 局部变量
- 相同点:
- 定义变量的格式:数据类型 变量名 = 变量值
- 先声明后使用
- 都有其作用的作用域
- 不同点:
- 在类中声明的位置不同
- 属性:直接定义在类的一对{}内
- 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
- 关于权限修饰符的不同
- 属性:可以在声明属性时,指明其权限,使用权限修饰符
- 局部变量:不可以使用权限修饰符(其实用方法来代替了…)
- 默认初始化值的情况
- 属性:类的属性,根据其类型,都有默认的初始化值,跟数组的一样
- 局部变量:没有默认初始化值,在使用之前一定要显式赋值
- 在内存中加载的位置不同
- 属性:加载到堆空间中(非static)
- 局部变量:加载到栈空间中
public class HelloWorld{
int age;
String name;
public static void main(String[] args){
int a = 10;
HelloWorld agent = new HelloWorld();
agent.name = "tgk";
agent.age = 18;
}
}
方法
也称为成员方法、method、函数
//四种例子
public void eat(){}
public void sleep(int hour){}
public String getName(){}
public String getNation(String nation){}
public class HelloWorld{
//String是返回的数据类型,int是输入的数据类型
public String isOddNumber(int num){
if(num % 2 == 0)
return "双数";
else
return "奇数";
}
// 主函数的入口!!!
public static void main(String[] args){
int a = 10;
HelloWorld method = new HelloWorld();
String str = method.isOddNumber(a);
System.out.println(str);
}
}
方法的重载
-
**定义:**在同一个类中,允许存在一个以上的同名方法,只要他们的参数个数或者参数类型不同即可
-
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系
public class OverLoad {
// 如下的四个方法都是构成了重载
public void getSum(int i, int j){
}
public void getSum(double i, double j){
}
public void getSum(String i, int j){
}
public void getSum(int i, String j){
}
}
可变个数的形参
// 从JDK5.0开始采用多个可变个数形参来定义方法,传入多个同一类型的变量
// 跟之前传递数组一样,通过遍历数组来获取值,如books.length
// 必须声明在末尾,即books必须在a后面
// 就跟python里的fun(*args)一样
public static void test(int a, String ... books);
// 与下面表示形式一样,就相当于一个数组
public static void test(int a, String[] books);
// 当与其他方法形参可以相同时,还是先以固定形参的方法为准!!!
变量的赋值(重点!)
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
递归方法的使用(了解)
- 递归方法,一个方法体内调用它自身
- 方法递归包含了一种隐式的循环,会重复执行某段代码,但无需循环控制,递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环
public class RecursionTest {
public static void main(String[] args) {
System.out.println(new RecursionTest().getSum(100));
}
// 计算1~100的累加和,用递归
public int getSum(int n){
// 实际上这个if就是终止条件
if(n == 1){
return 1;
}else{
return n + getSum(n - 1);
}
}
}
构造器
作用
- 创建对象
- 初始化对象的属性
说明
- 如果没有显式的定义类的构造器,则系统默认提供一个空参的构造器,权限跟类的权限相同
- 定义构造器的格式:权限修饰符 类名(形参列表){ }
- 一旦显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
- 一个类中,至少有一个构造器
public class HelloWorld{
public static void main(String[] args){
// 创建类的对象:new + 构造器
Student stu = new Student(18);
System.out.println(stu.number);
}
}
class Student{
// 属性
int number;
// 构造器,这个是默认的空的
public Student(){
}
// 可以写多个,也称作重载
public Student(int n){
number = n;
}
// 方法
public void eat(){
System.out.println("吃饭");
}
}
代码块
- 用来初始化类、对象
- 静态代码块
- 内部类可以有输出语句
- 随着类的加载而执行,只执行一次
- 初始化类的信息
- 非静态代码块
- 内部类可以有输出语句
- 随着对象的创建而执行,每创建一次对象都执行一次
- 创建对象时,对对象的属性等进行初始化
- 由父及子,静态先行
//静态代码块
static{
System.out.println("静态代码块");
}
//非静态代码块
{
System.out.println("非静态代码块");
}
内部类
- Java中允许将一个类A声明在另一个类B中,则类A是内部类,类B称为外部类
- 成员内部类(静态、非静态)和局部内部类(方法内、代码块内、构造器内)
- 成员内部类
- 作为外部类的成员—>调用外部类的结构,static修饰,四种权限修饰符修饰
- 作为一个类—>类内定义属性、方法、构造器,final和abstract修饰
- 实例化成员内部类的对象
// Dog和Brid是Person的两个内部类
// 创建Dog实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类)
Person p = new Person();
Person.Brid brid = p.new Brid();
bird.sing();
- 开发中局部内部类的使用,就是在方法中新建类,通过接口,返回接口的一个实现类(外面不需要这个类)
三大特性
封装
功能
- 隐藏功能的实现细节
- 利用对象和方法是实现封装的直接途径
- 良好的封装可以让代码更容易阅读和维护
体现
- 将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
- 不对外暴露的私有的方法
- 单例模式
对成员变量进行读写封装
//先私有化成员变量
private String name = "tt";
//getter的书写格式为:public 成员变量类型 get成员变量名() PS:首字母大写
public String getName(){
//this关键词可以不用
return name;
}
//setter的书写格式为:public void set成员变量名(新的数据参数) PS:首字母大写
public void setName(String n) {
(this.)name = n;
//如果输入的名称和类中的不同,可以不用this
}
权限修饰符
访问修饰符用于控制类(只能用private和default)、成员变量、方法、构造器、内部类的访问范围
权限修饰符 | 名称 | 说明 |
---|---|---|
private | 私有 | 只能在类的内部访问 |
(default) | 默认 | 相同包的其他位置可以访问 |
protected | 继承 | 只有继承的子类能访问到 |
public | 公有 | 在任何地方都能访问 |
继承
功能
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的拓展
- 为之后多态性的使用,提供了前提
说明
- 子类、派生类、subclass
- 父类、超类、基类、superclass
- 一旦子类A继承父类B以后,子类A就获取了父类B中声明的所有的属性和方法,特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,但因为封装性的影响,子类不能直接调用父类的结构
- 子类继承以后,可以定义自己特有的属性和方法
- 所有java类都直接或间接继承于java.lang.Object类
//extends关键词用于类的继承
//extends关键词后面是“父类”。前面是“子类”
public class Student extends Person //Student继承person
方法的重写
- 子类继承父类之后,可以对父类中同名同参的方法进行覆盖操作(跟重载区别开!)
- 重写之后,当创建子类对象调用重写的方法时,执行的是子类中重写的方法
- 子类重写方法的权限不小于(>=)父类的权限,但是不能重写父类中为private的方法
- 返回值类型要求
- 父类被重写的返回值类型是void型,子类中重写的方法也只能是void型
- 父类被重写的返回值类型是A类型,子类中重写的方法可以是A类或者A类的子类(引用数据类型)
- 父类被重写的返回值类型是基本数据类型,子类中重写的方法必须是相同的基本数据类型
- 子类重写的方法抛出的异常类型不大于(<=)父类被重写的方法抛出的异常类型
- 子类父类中的同名同参的方法要么都声明为static(考虑重写),要么都声明为static(不是重写)
多态
一个事物的多种形态
理解
外接各种数据库的对象的时候,可以外接MySQLConnection和OracleConnection,在写方法时只需要写一个以父类Connetion对象为形参的方法,外面实际传递的时候可以传MySQLConnection或OracleConnection的对象,就没必要针对于不同的子类重写不同的方法!(真tm妙~)
说明
-
使用前提
- 类的继承关系
- 方法的重写
-
对象的多态性
- 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
- 对象的多态性只适用于方法,不适用于属性(编译和运行都看左边)
-
多态的使用
- 虚拟方法调用,有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行期,实际上执行的是子类重写父类的方法(编译看左,运行看右)
-
多态是运行时行为,也称为动态绑定
// Person是父类,Student是子类
public class MulTest {
public static void main(String[] args) {
// 对象的多态性:父类的引用指向子类的对象
Person p = new Student();
// 多态的使用,当调用子父类同名同参的方式时,
// 执行的是子类重写父类的方法 --虚拟方法调用
p.eat();
// p.study(); 会报错,只能调用Person类的方法
}
}
其他关键字
this
- this可以同来修饰属性、方法、构造器,通常情况下都省略了
- this修饰属性和方法时可以理解为:当前对象
- 显式的使用"this(形参列表)"方式,来调用本类中指定的其他构造器,必须放在首行
// 构造器,这个是默认的空的
public Student(){
// Student初始化时需要考虑以下问题(假设好多代码)
// 如果在每个构造器里都写,会冗余
}
public Student(int number){
this(); // 直接调用空的构造器,上面初始化的代码只需要写一遍
this.number = number;
}
super
- 理解为:父类的,可以用来调用属性、方法、构造器
- 可以在子类的方法或构造器中,通过使用"super.属性"或"super.方法"的方式,来显式的调用父类中的属性或方法
- 可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的构造器,必须放在首行
- 在构造器的首行,没有显式的声明"super(形参列表)“或"this(形参列表)”,则默认调用父类中空参的构造器
public class Student extends Person{
String major;
public Student(){
// 调用父类中空参的构造器
super();
}
public Student(int age, String name, String major){
// 直接调用父类中的构造器
super(age, name);
this.major = major;
}
public void eat(){
// 调用了父类中的eat方法
super.eat();
System.out.println("学生应该吃有营养的食物!");
}
}
- **子类对象实例化的过程:**通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,仅为调用父类的父类的构造器…直到调用了java.lang.Object类中的空参构造器,因此才可以看到内存中有父类的结构,子类对象次啊可以调用
package
功能
- 把功能相似或相关的类放到一个包里(就是一个文件夹)
- 使用package声明类或接口所属的包,声明在源文件的首行
- 包也采用了树形目录的存储形式,每"."一次,就代表一层文件目录
命名规则
- 采用"逆域名法"进行命名,用"."分割,单词全部小写
- 标准格式:域名后缀,组织机构名,项目名(模块名),包的职能
- 同一个包下,不能命名同名的接口、类
例如:com.imooc.weather.ui
package com.imooc.demo.demo1; //声明下面的类是属于哪个包
import com.imooc.demo.demo2.Demo2; //导入别的包里面具体的类名
public class Demo1 {
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
}
}
import
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,可以并列写出
- 可以使用"xxx.*"的方式,导入xxx包下的所有结构
- 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
- 如果在源文件中使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示
- import static:导入指定类或接口中的静态结构(属性、方法)
instanceof
// 对象的多态性:父类的引用指向子类的对象
Person p = new Student();
// p不能使用Student类的方法,可以使用向下转型,使用强制类型转换符
Student p1 = (Student) p;
// 此时p1就是Student类的对象了
// 使用强制转换是需要用instanceof来判断是否能转换成功!
- a instanceof A:判断对象a是否时类A的实例,如果是,返回true,如果不是,返回false
- 如果a instanceof A返回true,a instanceof B也返回true,则A和B是子父类的关系
static
- static可以用来修饰:属性、方法、代码块、内部类
- 用static修饰的属性称为静态变量(类变量)
- 当创建了类的多个对象,多个对象共享同一个静态变量,一个对象改,其他都改
- 静态变量随着类的加载而加载,可以通过"类.静态变量"的方式来调用,要早于对象的创建
- 类只会加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中
- 用static修饰的方法称为静态方法(类方法)
- 静态方法随着类的加载而加载,可以通过"类.静态方法"的方式来调用
- 静态方法中,只能调用静态的方法或属性
- 如何确定使用静态变量和静态方法
- 属性是可以被多个对象所共享的;类中的常量也常常声明为static
- 操作静态属性的方法,通常设置为static;工具类中的方法习惯上声明为static(Math、Arrays、Collections)
final
- 可以修饰类、方法、变量(大写)
- final修饰类:此类不能被其他类所继承,如String类、System类、StringBuffer类
- final修饰方法:表明此方法不能被重写
- final修饰变量:此时的“变量”就称为一个常量
- final修饰属性:可以赋值的位置有:显式初始化、代码块中初始化、构造器中
- final修饰局部变量:使用final修饰形参时,表明此形参是个常量,当调用此方法时,给常量形参赋一个实参,一旦赋值就只能在方法体内使用,但不能重新赋值
- static final属性:全局常量
abstract
-
当父类过于基础以后,就可以抽象成只表达类特性的类,不能创建对象,只能通过子类重写方法来实现功能
-
修饰的结构:类、方法
-
abstract修饰类(抽象类)
- 此类不能实例化
- 一定有构造器,便于子类实例化的调用
-
abstract修饰方法(抽象方法)
- 只有方法的声明,没有方法体
- 包含抽象方法的类,一定是抽象类,反之,抽象类中可以没有抽象方法的
- 若子类重写了父类中所有的抽象方法后,此子类才能实例化;若子类没有重写父类中所有的抽象方法,则此子类也是个抽象类,需要使用abstract修饰
public abstract String Person();// 没有方法体,必须保证类是抽象类,否则就能创建对象调用方法了
- 注意点
- 不能用来修饰属性、构造器等结构
- 不能用来修饰私有方法、静态方法(子父类中同名同参的静态方法不认为是重写)、final方法、final的类
interface
- Java中,接口和类是并列的两个结构
- 定义接口中的成员
- JDK7及以前:只能定义全局常量和抽象方法
- 全局常量:public static final,但是书写时可以省略不写
- 抽象方法:public abstract
- JDK8中,除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(default)
- 接口中定义的静态方法,只能通过接口来调用
- 用过实现类的对象,可以调用接口中的默认方法,如果实现类重写了接口中的默认方法,调用时仍然调用重写后的方法
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参的方法,那在子类没有重写此方法的情况下,默认调用的是父类中的同名同参的方法
- 如果实现类实现了多个接口,多个接口中定义了同名同参的方法,实现类中没有重写此方法的情况下会报错----->接口冲突
- 在子类(或实现类)的方法中调用父类、接口中被重写的方法:super.方法名;接口名.super.方法名
- JDK7及以前:只能定义全局常量和抽象方法
- 接口中不能定义构造器!意味着接口不可以实例化!
- Java开发中,接口通过让类去实现(implements)的方式来使用,如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化,否则此实现类仍然是抽象类
- 子类中覆盖父类的方法叫重写,子类覆盖抽象父类或接口的方法叫做实现(康师傅,您真细~)
- Java类可以实现多个接口—>弥补了Java单继承性的局限性
- 接口与接口之间可以继承,而且是多继承
// 接口的例子
public class InterfaceTest{
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
Plane plane = new Plane();
plane.fly();
}
}
interface Attackable{
void attack();
}
interface Flyable{
// 全局常量
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1; // 省略了public static final
// 抽象方法
public abstract void fly();
void stop(); // 省略了public abstract
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
// 类可以实现多个接口
class Bullet extends Object implements Flyable, Attackable{
// 要重写所有接口中的抽象方法才能创建对象
@Override
public void attack() {
}
@Override
public void fly() {
}
@Override
public void stop() {
}
}
//实现接口和接口之间的多继承操作
interface AA{}
interface BB{}
interface CC extends AA,BB{}
- 接口使用上也满足多态性
// 多态性的体现
public class USBTest {
public static void main(String[] args) {
// 四种接口的使用方法
Computer computer = new Computer();
// 1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
computer.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
computer.transferData(new Printer());
// 3.创建了接口的匿名实现类的非匿名对象
USB phone = new USB() {
@Override
public void start() {
}
@Override
public void stop() {
}
}; // 有大括号...
computer.transferData(phone);
// 4.创建了接口的匿名实现类的匿名对象
computer.transferData(new USB() {
@Override
public void start() {
}
@Override
public void stop() {
}
});
}
}
class Computer{
public void transferData(USB usb){
usb.start();
System.out.println("具体数据传输的细节");
usb.stop();
}
}
interface USB{
// 定义了USB接口的方法
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开始工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
- 接口实际上就是定义
- 开发中,体会面向接口编程
异常处理
-
异常体系结构—>java.lang.Throwable
- java.lang.Error:一般不编写针对性的代码进行处理
- java.lang.Exception:可以进行异常的处理
- 编译时异常(checked):IOException、ClassNotFoundException
- 运行时异常(unchecked):NullPointerException、ArrayIndexOutOfBoundsException
-
抓抛模型:
- ”抛“:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,一旦抛出对象以后,其后的代码便不再执行
- 系统自动生成的异常对象
- 手动的生成一个异常对象,并抛出(throw)
- ”抓“:可以理解为异常的处理方式,有try-catch-finally和throws
- ”抛“:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,一旦抛出对象以后,其后的代码便不再执行
-
try-catch-finally使用
- finally是可选的
- 使用try将可能出现异常代码包装起来,一旦出现异常就会生成一个对应异常类的对象,通过catch对抛出的异常对象的类型进行匹配
- 一旦满足处理完成,便跳出当前的try-catch结构(没有finally),继续执行其后面的代码
- catch中的异常类型如果没有子父类的关系,则声明顺序无所谓,如果满足子父类关系,则子类一定声明在父类前面
- 常用异常对象处理的方式:String getMessage()、printStackTrace()
- try-catch-finally结构可以嵌套
-
finally使用
- finally中声明的是一定会执行的代码,即使catch中又出现异常了,try中又return语句,catch中有return语句等情况,在try-catch结构结束之前执行
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,需要自己手动的资源的释放,此时就要声明在finally当中
-
体会:
- 使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错,相当于我们将一个编译时可能出现的异常延迟到运行时出现
- 开发中,由于运行时异常比较常见,所以通常就不针对与运行时异常编写try-catch结构,对于编译时异常,要考虑~
public class ExceptionTest { @Test public void test1(){ String str = "123"; str = "abc"; try{ int num = Integer.parseInt(str); }catch(NumberFormatException e){ // System.out.println("出现数值转换异常了,莫慌..."); // String getMessage() System.out.println(e.getMessage()); // printStackTrace e.printStackTrace(); }catch(NullPointerException e){ // catch中又有异常会直接跳出去报错 int[] arr = new int[10]; System.out.println(arr[10]); } // finally代码一定会被执行 finally { System.out.println("我好帅啊~~"); } } }
-
throws + 异常类型
- ”throws + 异常类型“写在方法的声明处,指明此方法执行时可能会抛出的异常类型,一旦方法体执行时出现异常,突然会在异常代码处生成一个异常类的对象,此对象满足throws后的异常类型时,就会被抛出,后续代码不会执行
-
**体会:**try-catch-finally:真正的将异常给处理掉了,throws的方式只是将异常抛给了方法的调用者,并没有将异常处理掉
如何选择try-catch-finally和throws?
- 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式去处理
- 执行的方法a中,先后又调用了另外的几个方法,这几个方法时递进关系执行的,我们建议这几个方法用throws处理,而在执行的a中用try-catch方式处理
手动抛出异常
class Students{
private int id;
// 异常的处理,声明在方法处
public void regist(int id) throws Exception {
if(id > 0){
this.id = id;
}else
// System.out.println("你输入的数据非法!");
// 声明异常的对象,手动抛出异常
throw new Exception("你输入的数据非法!");
}
}
零碎知识点
== 和equals()的区别
== 运算符
- 可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等(不一定类型要相同)
- 如果比较的是引用数据类型变量,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
equals()方法
- 是一个方法,而非运算符
- 只能适用于引用数据类型
- Object类中equals()和==的作用是相同的
- 像String、Date、File、包装类等都重写了Object类中equals()方法,重写以后不再是比较两个对象的地址,而是比较两个对象实例属性是否相同
单元测试
此时单元测试方法,方法的权限是public,没返回值,没形参
idea暂时没看
包装类(Wrapper)
- 针对八种基本数据类型定义相应的引用类型–包装类(封装类)
- 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
- 数值型基本数据类型的包装类都有父类Number
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
基本数据类型、包装类、String类型相互转化
import org.junit.Test;
public class WrapperTest {
// 基本数据类型--->包装类
@Test
public void Test1(){
int num = 10;
// 调用包装类的构造器
Integer int1 = new Integer(num);
// 打印int1和int1.toString()一样
System.out.println(int1.toString());
// 也可以对只带数字的字符串创建对象
Integer int2 = new Integer("123");
System.out.println(int2);
}
// 包装类--->转换为基本数据类型
@Test
public void Test2(){
Integer int1 = new Integer(12);
// 调用包装类的xxxValue()
int i1 = int1.intValue();
System.out.println(i1);
}
// JDK 5.0新特性:自动装箱和自动拆箱
@Test
public void Test3(){
// 自动装箱:基本数据类型--->包装类
// 不用再调用包装类的构造器
int num = 10;
Integer int1 = num;
// 自动拆箱:包装类--->转换为基本数据类型
int num1 = int1;
}
// 基本数据类型、包装类--->String类型
@Test
public void Test4(){
int num = 10;
// 方式1:连接运算
String str1 = num + "";
// 方式2:调用String重载的valueOf(Xxx xxx)
float f = 12.3f;
String str2 = String.valueOf(f);// "12.3"
}
// String类型--->基本数据类型、包装类
@Test
public void Test5(){
String str1 = "123";
// 调用包装类的parseXxx()
int num = Integer.parseInt(str1);
System.out.println(num + 1);
}
}
单例设计模式
- 就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
- 两种模式:饿汉式和懒汉式
import org.junit.Test;
public class Singleton {
@Test
public void SingletonTest1(){
Bank bank = Bank.getInstance();
}
@Test
public void SingletonTest2(){
Order order = Order.getInstance();
}
}
// 饿汉式
class Bank {
// 1.私有化类的构造器
private Bank(){
}
// 2.内部创建类的对象,也可以直接理解为类的属性(一个对象)
// 4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
// 3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
// 懒汉式
class Order{
// 1.私有化类的构造器
private Order(){
}
// 2.声明当前类对象,没有初始化
// 4.此对象也必须声明为static
private static Order instance = null;
// 3.提供公共的静态的方法,返回类的对象
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
- 区分饿汉式和懒汉式
- 饿汉式:好处:饿汉式是线程安全的;坏处:对象加载时间过长
- 懒汉式:好处:延迟对象的创建;坏处:目前来说是线程不安全的
懒汉式线程安全的写法
class Order{
private Order(){
}
private static Order instance = null;
public static Order getInstance(){
// 懒汉式线程安全的写法
// 方式1:效率较差
// synchronized(Order.class) {
// if (instance == null) {
// instance = new Order();
// }
// return instance;
// }
// 方式2:效率稍高
// 单例模式加锁需要两个if判断
if(instance == null) {
synchronized (Order.class) {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
return instance;
}
}