面向对象基础
两种编程思想
面向过程编程
比如c语言,代码写在文件中,没有类的概念,程序的组成靠函数(function),
编程的思路按业务逻辑顺序写若干函数,依次调用函数来实现功能
重点关注业务的过程和步骤
面向对象编程
Object Oriented Programming (OOP),面向对象编程
面向对象编程语言中都有类的概念,代码的组织靠类(代码的最小组织单位是类),面向对象编程中执行一个功能的代码叫方法(method)
编程思路调用不同功能的类的方法来实现业务功能
重点关注业务需要哪些功能的类来组成
举例:比较两种思想的不同之处
打扫教室的卫生:
面向过程:思考打扫教室卫生的步骤, 椅子放到桌子上 - > 扫地上垃圾 -> 拖地 -> 椅子放下来 -> 擦窗户 -> 擦桌椅
安排任务: 谁做什么
面向对象:思考打扫教室卫生有哪些任务:地面,桌椅,门窗
安排: 谁适合做对应的任务。 地面劳动强度大,派强壮的同学; 擦桌椅需要细心,派女生做;门窗比较高,派高个子同学
找的人具备业务能力,不需要教他,安排下去基本就不用管
总结
- 面向过程:亲力亲为,侧重于分析和处理步骤
- 面向对象:所有的事情交给相应的对象(具备能力)去完成,侧重于分析完成任务需要哪些(什么样)对象
类和对象
类(Class)
具有相同属性和行为的对象的集合,它是一个抽象的概念,不指向任何具体的实物
属性:描述对象的特征,在程序中,体现成员变量
行为: 对象具有的动作或能力,在程序中,体现方法。
例如下列这些都是类:
猫, 学生, 树, 桌子…
类定义
程序中用关键字class定义类
语法结构:
[修饰符] class 类名{
//属性(成员变量)
//行为(方法)
}
public class Car {
// 定义属性(成员变量)
String brand; // 品牌
String color; // 颜色
int seat; //座位数
// 定义方法(行为)
void go(){
System.out.println("在行驶...");
}
void stop(){
System.out.println("停下来了...");
}
void fly(){
System.out.println("正在飞...");
}
}
对象(Object)
对象是类的一个具体的实例, 是类的具体的表现
- 创建对象的语法:
类名 对象名 = new 类名([参数]);
//Car car = new Car();
- 读写属性
对象名.属性名
// car.brand = “长安”;
//System.out.println(car.brand + car.color + car.seat);
- 调用方法
对象名.方法名([参数]);
//car.go();
// 创建一个Car类的对象(实例化)
Car car = new Car();
// 设置对象的属性
car.brand = "长安";
car.color = "白色";
car.seat = 5;
System.out.println(car.brand + car.color + car.seat);
// 执行对象行为(调用对象的方法)
car.go();
car.stop();
car.fly();
类和对象之间的关系
类是对象的集合,是抽象概念;对象是类的具体实例。
通过new关键字创建(实例化)对象
成员变量和局部变量区别
成员变量
定义在类中的变量,称为成员变量,也叫属性,
它跟数组元素一样,有默认, 所以成员变量可以不初始化直接使用
作用域是整个类
对象被垃圾回收时,成员变量就消失
public class Car {
// 定义属性(成员变量)
String brand; // 品牌
String color; // 颜色
int seat; //座位数
成员变量默认值(跟数组元素的默认值相同)
数据类型 | 默认值 |
---|---|
整型 | 0 |
浮点型 | 0.0 |
布尔型 | false |
字符型 | 不可见字符(‘\u0000’) |
引用类型 | null |
局部变量
定义在方法中的变量,称为局部变量
局部变量没有默认值,所以必须要先初始化,再使用
局部的作用域在它所在的大括号内有效
方法执行完成后,局部变量就消失
public static void main(String[] args) {
int i = 0;
String str = "张三";
for (int j = 0; j < 10; j++) {
}
练习:
1. 创建一个学生类
属性: 姓名, 年龄, 性别
行为: 学习, 考试, 休息
public class Student {
String name;
int age;
char sex;
void study(){
System.out.println("学习");
}
void exam(){
System.out.println("考试");
}
void rest(){
System.out.println("休息");
}
}
2.猜数游戏(面向对象编程思想)
随机产生一个数字(1~100), 猜测并输入一个数字,判断是否猜中,猜中就结束, 如果没有猜中给出提示(猜的数字比生成的数字大了还是小了),继续猜数
(思考过程:
需要哪些对象? 对象列出来,再判断是否需要实现类?
人, 游戏对象(GuessNum)
每个对象有哪些属性和行为?
GuessNum属性:生成数字
猜的数字
行为:
产生随机数
接受输入
判断结果,提示
)
- GuessNum.java
package com.hqyj;
import java.util.Scanner;
/**
* 猜数游戏
* 随机产生一个数字(1~100), 猜测并输入一个数字,判断是否猜中,猜中就结束,
* 如果没有猜中给出提示(猜的数字比生成的数字大了还是小了),继续猜数
*/
public class GuessNum {
int initNum; //随机生成的数字
int givenNum; //玩家给出的数字
/**
* 生成随机数(1~100)
*
*/
void generateNum(){
double random = Math.random();
initNum = (int) (random * 100);
}
/**
* 接受玩家输入的数字
*/
void acceptInput(){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您猜的数字:");
givenNum = scanner.nextInt();
}
/**
* 判断玩家猜的数字是否正确
* 并提示
* @return 猜对了返回false; 猜错了返回true
*/
boolean checkNum(){
boolean b = true;
if(givenNum == initNum){
System.out.println("恭喜您,猜对了");
b = false;
}else if(givenNum > initNum){
System.out.println("您猜的数大了,请重新猜");
}else{
System.out.println("您猜的数小了,请重新猜");
}
return b;
}
}
- PlayGuessNum.java
package com.hqyj;
public class PlayGuessNum {
public static void main(String[] args) {
GuessNum guessNum = new GuessNum();
//产生随机数
guessNum.generateNum();
do{
//用户猜数
guessNum.acceptInput();
}while (guessNum.checkNum()); //判断猜的是否正确
}
}
构造方法
构造方法是类里一个特殊的方法:
- 方法名跟类名相同
- 没有返回值(连void都没有,没有返回类型)
- 可以有参数,也可以没有参数
- 如果一个类,没有写构造方法,默认提供一个无参数的构造方法
- 一个类可以有多个构造方法,每个构造方法的参数个数可以不一样
- 一旦一个类写了构造方法,默认的无参的构造方法不再生效
- 构造方法不能通过对象直接调用,在实例化的时候自动调用
默认无参构造方法如下:
public Car() {
}
//空构造方法new对象
Car car = new Car();
有参构造方法:
//构造方法的作用给属性赋初始值
public Car(String brand, String color, int seat) {
// this代表当前对象
this.brand = brand;
this.color = color;
this.seat = seat;
}
//有参数的构造方法new对象
Car car1 = new Car("奔驰", "黑色", 5);
- IDEA中创建构造方法
代码空白地方右键 - 》 generate
选constructor
选择要生成构造方法的属性
按住Ctrl, 点击属性实现多选
或按住Shift, 点击最后一个,从开始选择到最后
如果一个都不想选,点“Select None”按钮
POJO
POJO(Plain Ordinary Java Object) 简单Java类。 也叫Java Bean, 或实体类
主要用于描述数据,比如数据库中记录
只有属性和get, set方法, 没有其它的业务逻辑方法
员工实体类(POJO)
package com.hqyj;
/**
* 员工实体类
*/
public class Emp {
String name; //员工姓名
int sex; //员工性别
int age; //员工年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package和import
package
Java中的包,用于区分同名的类,相当于windows系统的目录(文件夹)
为了避免package相同,一般用公司的域名的逆序作为package,例如:华清远见的域名是hqyj.com, 包名就写为com.hqyj
package放到类文件的第一行,语法: package xxx.xxxx
package都是用小写,中间用点号分割,每个点号分割开的一段,在磁盘上会生成对应的文件夹
import
在使用一个别的包下面的类的时候,需要用import关键字导入包
import语句写在package之后, 在class定义之前
import语句一次可以导入一个类,或者用通配符*导入多个类
//导入java.util包下的Scanner类
import java.util.Scanner; //带上包名的完整类名称为全限定名
//批量导入java.util包下的所有类
import java.util.*;
在idea中,如果new 对象时用了代码补全功能,它自动帮我们导入包。有同名的类时,要让我们选择导入哪个包下的类
如果使用java.lang这个包下的类,不需要导入,直接可以使用
面向对象三大特性(封装、继承、多态)
封装
使用private修饰符对属性进行修饰,限制属性的访问权限,只有类内部能够访问这样的属性,其它的类通过对象.属性的方式是无法访问的。从而对对象的状态(数据)进行保护,外部类要访问的话需要通过调用get/set的方法,保证属性统一访问。
- 学生类Student
有一些属性,都是用private修饰
提供get, set方法访问属性。 在set时候可以限制赋值的内容;在get时可以返回指定格式的数据,转换和限制逻辑封装在类的方法中
package com.hqyj;
public class Student {
//private 就是私有修饰符
private String name;
private int sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
String result = sex == 1 ? "男" : "女";
return result ;
}
public void setSex(int sex) {
//限制性别的赋值,不允许随便赋值
if(sex < 0 || sex > 1){
throw new RuntimeException("性别只能为0或者1");
}
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- 使用学生类
使用学生类时不能直接访问属性,必须通过get,set方法。
如果给属性赋值,不符合要求(比如性别就不能设置0和1以外的值),就报异常。
用get方法读取属性,会读取到学生类中转换之后的内容(比如这里的男, 女)
package com.hqyj;
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
// student.name = "小李"; //私有成员变量,不能直接用对象.属性方式访问
student.setName("小李"); //私有属性只能通过get/set方法访问
student.setAge(22);
student.setSex(0);
System.out.println(student.getSex());
}
}
访问修饰符
控制访问权限的关键字称为访问修饰符,有private , default, protected, public
访问修饰符可以用在类, 成员变量以及方法前面。 局部变量不需要访问修饰符
同类 | 同一个包 | 子类 | 其它类 | |
---|---|---|---|---|
private | √ | × | × | × |
default(默认,不写修饰符) | √ | √ | × | × |
protected(受保护) | √ | √ | √ | × |
public | √ | √ | √ | √ |
继承
A类可以通过extends关键字继承B类,继承之后A类可以有有一些B类中的属性或方法,实现代码重复利用
语法:
[修饰符] class 子类 extends 父类{
}
子类也叫派生类, 衍生类, subClass
父类也叫基类,超类,superClass
- 继承的特点
继承只能单继承,只能有一个父类; 一个父类,是可以有多个子类
继承可以多重继承,A继承B, B继承C
Object是所有类的父类(所有类继承至Object)
子类可以重写(覆盖)父类的方法
创建子类时,会先执行父类的构造方法
代码案例
父类Shap定义了一个成员变量name, 一个方法area()
子类Rectangle继承了父类Shap, Rectangle自动有name属性和area()方法
子类中定义自己的属性,也可以写自己的方法, 还可以重写父类继承来的方法
- 父类Shap
package com.hqyj;
/**
* 形状
*/
public class Shap {
public String name;
public void area(){
System.out.println("显示图形面积");
}
}
- 子类Rectangle
package com.hqyj.other;
import com.hqyj.Shap;
public class Rectangle extends Shap {
private float width; //矩形的长
private float height; //矩形的高
public Rectangle(float width, float height) {
this(); //调用无参构造方法, 在构造方法中调用其它构造方法,要写在第一行
this.width = width;
this.height = height;
}
public Rectangle() {
this.name = "矩形";
}
@Override //重写,覆盖父类的方法
public void area() {
float area = width * height;
System.out.println(area);
}
}
- 测试代码
package com.hqyj.other;
public class RecTangleTest {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(5, 4);
rectangle.area();
System.out.println(rectangle.name);
}
}
重写和重载
重写override(覆盖)
子类继承父类时,子类对父类的方法代码重写写过,就叫重写。
重写的特性:
- 方法名、参数列表(个数,类型,顺序)、返回类型必须要一致
- 访问权限(修饰符)不能比父类更小(跟父类的访问权限一样或者更大)
- 如果要抛出异常,不能比父类的异常更大
重载overload
在一个类(不一定要继承)中,如果有多个方法的方法名相同,参数列表不同(个数,类型,顺序),这些同名的方法就称为重载
重载的特性:
- 方法名相同
- 参数列表不同(个数,类型,顺序)
package com.hqyj;
public class Rect {
/**
* 计算正方形的面积
*/
public void area(int width){
System.out.println(width * width);
}
/**
计算长方形的面积
*/
public void area(int width, int height){
System.out.println(width * height);
}
}
this和super关键字
这两个关键字都可以指代对象或构造方法
当成对象用
public Shap() {
//this用在构造方法里,代表当前对象
this.name = "形状";
System.out.println(this.name);
}
public void setAge(int age) {
//this用在set方法里,代表当前对象。
//为了区分成员变量和局部变量,因为两个都叫age
this.age = age;
}
public void parentArea(){
//在子类中访问父类的方法,用super.方法()
super.area();
}
当成构造方法使用
-
构造方法中调用构造方法,用this代替构造方法名。
-
构造方法中调用构造方法,调用语句只能放在第一行
-
子类要调用父类的构造方法,用super()
-
如果子类的构造方法没有调用父类的构造方法,默认会调用父类的无参的构造方法
-
如果父类没有无参构造方法,在子类的构造方法中必须要调用父类的有参的构造方法
Parent.java
package com.hqyj;
public class Parent {
private int age;
private String name;
private int sex;
public Parent() {
System.out.println("无参");
}
public Parent(String name, int sex) {
this();
this.name = name;
this.sex = sex;
System.out.println("两个参数");
}
public Parent(int age, String name, int sex) {
this(name, sex);
this.age = age;
System.out.println("三个参数");
}
public static void main(String[] args) {
Parent parent = new Parent(22, "张三", 1);
}
}
package com.hqyj;
public class Son extends Parent{
private int age;
private String name;
private int sex;
public Son() {
super();//执行父类的构造方法,不写也会自动调用
System.out.println("son 无参");
}
public Son(String name, int sex) {
this.name = name;
this.sex = sex;
System.out.println("son 两个参数");
}
public Son(int age, String name, int sex) {
super(age, name, sex);
this.age = age;
this.name = name;
this.sex = sex;
System.out.println("son 三个参数");
}
public static void main(String[] args) {
// Son son = new Son();
new Son(22, "小王", 0);
}
}
相关面试题
- 构造方法被调用的时候,一定会创建出对象,这个说法正确吗?
错误。 子类的构造方法执行时,要先调父类的构造方法,这时候父类并没有创建出对象
-
重写和重载的区别?
-
构造方法能重载吗? 能重写吗?
可以重载(一个类可以有多个不同参数的构造方法)
不可以重写
多态
一个类在编译时和运行时同一个方法呈现出不同状态。
比如:动物类有一个发出叫声的方法, 它的子类有猫类,猫的叫声是喵喵喵;还有一个子类是狗类,狗的叫声是汪汪汪,
现在有一个动物类的对象,调用发出叫声的方法,有可能叫:喵喵喵, 也有可能会叫:汪汪汪
形成多态的条件:
- 有继承关系,有多个子类,子类重写父类的方法
- 父类类型定义的变量,用子类的实例给它赋值
多态的用处:
统一管理,无限适配或扩展,
例如:windows操作系统有打印功能, 规定好打印的方法,不同类型的打印机(针式,喷墨,激光,黑白,彩色。。。)只要继承重写打印方法,就可实现自动适配所有的打印机
- Animal.java
package com.hqyj;
public class Animal {
public void shout(){
System.out.println("动物在叫");
}
}
- Cat.java
package com.hqyj;
public class Cat extends Animal{
@Override
public void shout() {
// super.shout();
System.out.println("喵喵喵");
}
}
- Dog.java
package com.hqyj;
public class Dog extends Animal{
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
- 测试代码
Animal animal1 = new Animal();
animal1.shout(); //动物类的叫声
Cat cat = new Cat();
cat.shout();
Dog dog = new Dog();
dog.shout();
System.out.println("========Animal animalCat = cat;=========");
Animal animalCat = cat;
animalCat.shout();//喵喵喵
System.out.println("======Animal animalDog = dog;=======");
Animal animalDog = dog;
animalDog.shout();//汪汪汪
向上造型
父类 变量 = new 子类();
父类定义的变量,用子类的实例对象赋值
注意: 向上造型时, 不能调用子类中自己添加的方法
System.out.println("========Animal animalCat = cat;=========");
Animal animalCat = cat;
animalCat.shout();//喵喵喵
System.out.println("======Animal animalDog = dog;=======");
Animal animalDog = dog;
animalDog.shout();//汪汪汪
向下造型
子类 变量 = (子类)父类的对象;
用父类的对象给子类变量赋值,向下造型必须用强制类型转换
向下造型分为几种情况:
-
直接new一个父类对象赋给子类变量, 不允许, 运行时会抛异常
-
父类对象是通过子类对象向上造型上去的实例,这种对象可以向下造型,也要强制类型转换,子类的所有方法都可以访问
-
跟2一样的做法,但是换了子类,如猫的子类的对象,向下造型成狗,不允许, 运行时抛出异常
// 向下造型
//1. 直接用父类创建的对象赋给子类变量,会抛异常
// Cat cat = (Cat) new Animal();
//2. 把通过向上造型得到的父类对象赋给子类对象,可以调用继承至父类的方法,也可以子类自己的方法
Animal animal1 = new Cat();
System.out.println("========向下造型=======");
Cat cat1 = (Cat) animal1;
cat1.shout();
cat1.eat();
//3. 造型时子类发生了变化
System.out.println("========向下造型换了子类对象========");
Animal animal2 = new Dog();
//Cat cat2 = (Cat) animal2; //会抛出异常
static 静态的
static也是一种修饰符,它表示静态的意思,它可以修饰属性、方法、代码块
用static修饰的属性或方法称为静态成员或方法,它是属于类的(不属于对象),所以可以不用创建对象直接使用
静态方法用于高频访问的方法(比如工具类的方法),调用方便
静态的属性和方法
package com.hqyj;
public class Parent {
public static float pi = 3.14F;
public static void area(float r){
System.out.println(pi * r * r);
}
}
静态属性和方法直接用类名访问
package com.hqyj;
public class ParentTest {
public static void main(String[] args) {
System.out.println(Parent.pi);
Parent.area(5);
}
}
- 静态属性用于所有对象共享的属性(它只有一份,保存类上的)
package com.hqyj;
public class Parent {
public static float pi = 3.14F;
public static int count;
private int num;
public static void area(float r){
System.out.println(pi * r * r);
}
public int getNum() {
return num;
}
public int getCount(){
return count;
}
public void setNum(int num) {
count = num;
this.num = num;
}
}
测试结果:
非静态成员是对象独有,互不干扰的
静态成员变量是所有对象共有的,任何一个对象改变了它,其它对象都能看到改变后的结果,它不属于对象,属于类
Parent parent1 = new Parent();
Parent parent2 = new Parent();
parent1.setNum(5);//parent1对象设置num为5, count=5
parent2.setNum(8); //parent2对象设置num为8, count=8
System.out.println(parent1.getNum());//5
System.out.println(parent1.getCount());//8
System.out.println(parent2.getNum());//8
System.out.println(parent2.getCount());//8
- 静态成员的访问条件
- 静态成员变量在非静态的方法里可以访问
- 静态方法中才能访问静态成员变量
- 静态方法不能访问非静态属性
public int getCount(){
return count;
}
public static void getData(){
// System.out.println(num); //静态的方法不能访问非静态的属性
System.out.println(count); //静态方法才能访问静态成员变量
}
- static修饰代码块
语法:
static{
//要执行的代码
}
静态代码块类加载时候执行(在构造方法之前), 跟它在类中的位置无关
静态代码块一般用于初始化、条件准备或运行其它方法之前必须要先执行的代码
static {
// 在类加载时执行一次
// 在类中的位置无关,一般会放在成员变量和方法之间
// 在构造方法之前执行
System.out.println("静态代码块");
}
JVM内存模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ug0yJob9-1660566044275)(D:\笔记\javaOOP03.assets\image-20220729145141256.png)]
Object对象及它的方法
Object类是所有类的父类,我们的类没有extends Object, 它都会默认继承Object
Object类有几个方法,所有的类都会继承这些方法,有需要的时候可以重写这些方法
- toString()
如果一个对象直接用于显示,它默认区调用toString(), 把返回的内容显示出来。
对象的toString()方法默认返回类的全限定名再加上对象的id,这个信息没有太大参考价值,所以我们常常重写类的toString()方法,返回属性的值
@Override
public String toString() {
return "Student{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
- equals() 和 hashCode()
hash是一种算法,根据一定的内容生成一个hash code值,用于判断两个内容是否一样
equals()方法判断两个对象是否内容相等,它依赖hashCode()方法
@Override
public boolean equals(Object o) {
// 首先比地址是否相等
if (this == o) return true;
// 要比较的对象是否是null,是null不相等;
// getClass() != o.getClass() 判断两个对象是否是同一个类创建的,如果不是肯定不相等
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return num == student.num;
}
@Override
public int hashCode() {
return Objects.hash(num);
}
测试
Student student = new Student();
student.setName("张三");
student.setNum(2019020120);
Student student1 = new Student();
student1.setNum(2019020120);
student1.setName("张三");
//对象用==比较,比较的是它地址
System.out.println(student == student1);
//用equals判断两个对象,equals方法返回true,就是相等
System.out.println(student.equals(student1));
== 与 equals
基本类型判断是否相等用==
引用(对象)类型用==判断,只是判断对象的地址,如果相等,说明是指向堆中的同一个对象
逻辑上判断两个对象是否相等,需要用equals方法,往往需要重写equals
注意: 字符串的字面量赋值时不会生成对象,而是存放在常量池,常量池中的字符串可共享
System.out.println("=========双等和equals========");
short s = 65;
int i = 65;
char c = 'A';
//基本类型用==比较, 它是比较的值, 跟类型无关
System.out.println(s == i);
System.out.println(i == c);
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2)); //true
String str3 = "xyz";
String str4 = "xyz";
//字面量赋值的字符串会保存在常量池,并不会创建一个对象
System.out.println(str3 == str4); //true
System.out.println(str3.equals(str4)); //true
- getClass()
返回类的Class类型对象。
每个类加载到方法区时都会生成一个Class类型的对象,这个对象负责访问类的字节码
- finalize()
gc(垃圾回收器)回收对象之前调用的一个方法。
- notify 及 wait等方法
这些方法是多线程使用的
final
final意思是最终的, 它也是修饰符, 它可以修饰类、成员变量、方法
- 修饰属性
- 修饰成员变量时要赋值, static final修饰的成员变量就是常量
- 修饰局部变量,可以先定义再赋值
- 不管时修饰哪一种变量,只能被赋值一次,再也不能被修改
/* 修饰成员变量 */
// static 加 final修饰成员,就是常量
public static final float pi = 3.14F;
//final的成员变量必须要初始化
// final的变量不能再被赋值(值初始化好之后就不能再改变)
public final int flag = 0;
/* 修饰局部变量 */
public int getNum() {
final int i;
i = 2;
//i = 3; //只能赋值一次,不能重复赋值
System.out.println(i);
- 修饰方法
final修饰的方法不能被重写
public final void test(){
}
- 修饰类
final修饰的类不能被继承
public final class Parent {
final, finalize, finally的区别
没有相通性,为一相同就是单词都是以fina打头,很容易混淆,经常被作为面试题
- final是一个修饰符(关键字), 被final修饰的属性不能被修改,被final修饰的方法不能被重写,被final修饰的类不能被继承
- finalize是Object对象的一个方法,这个方法在垃圾回收之前会被调用
- finally是异常语句的一个分支, finally分支的代码一定会被执行
try{
//可能会出异常的代码
}catch(){
//异常的处理代码
}finally{
//代码一定会被执行。 一般用于收关闭
}
JDK常用的类
String
//字符串的定义和初始化
String str1 = "abc"; //不创建对象,常量池中的常量
String str2 = new String("abc"); //创建一个实例对象
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2)); //true
// 字符串相关的方法
char c = str1.charAt(2); //返回指定下标的字符
System.out.println(c);
byte[] bytes = str1.getBytes(); //获取字符串的字节数组
System.out.println(Arrays.toString(bytes));
String str3 = str1.toUpperCase(); //字符串转大写字母
str1.toLowerCase(); //字符串转小写字母
System.out.println(str3);
str3 = str1.concat(str2); //拼接两个字符串
str3 = str1 + str2; //字符串拼接
System.out.println(str3);
/*
字符串创建之后不能改变长度,所以做字符串的拼接会不停创建新对象,性能不高的
如果遇到大量字符串的拼接, 换用StringBuffer(线程安全)或StringBuilder(线程不安全)
*/
long start = System.currentTimeMillis();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(str1);
stringBuffer.append(str2);
str3 = stringBuffer.toString();
System.out.println(str3);
long end = System.currentTimeMillis();
System.out.println("执行耗时:" + (end - start)); //用来调试程序执行的时间, 单位是毫秒
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str1);
stringBuilder.append(str2);
str3 = stringBuilder.toString();
System.out.println(str3);
boolean b1 = str1.endsWith("c"); //判断一个字符串是不是以某个字符串结尾
System.out.println(b1);
boolean b2 = str1.startsWith("a");//判断一个字符串是不是以某个字符串开头
System.out.println(b2);
b1 = str1.contains("b"); //字符串中是否包含某个字符串
System.out.println(b1);
str1 = "This is a string";
int i = str1.indexOf("string");//返回指定字符串第一次出现的起始下标
System.out.println(i); //10
i = str1.indexOf("is"); //2
System.out.println(i); //第一个is, 下标2
i = str1.lastIndexOf("is");//返回指定字符串最后一次出现的起始下标
System.out.println(i);//5
i = str1.indexOf("is", 3); //从下标3开始,找第一次出现指定字符串下标
System.out.println(i); //5
i = str1.indexOf("was"); //如果没有找到指定字符串,返回-1
System.out.println(i);
//数组的length是属性,字符串的length是方法
int length = str1.length(); //字符串的长度(有多少个字符)
System.out.println(length);
String substr = str1.substring(10); //一个参数,从这个位置开始,截取字符串结束
System.out.println(substr);
substr = str1.substring(10, 13); //两个参数,截取从指定位置开始,到指定位置结束的字符串(左闭右开区间)
System.out.println(substr);
str2 = " abc ";
str3 = str2.trim(); //去掉字符的前后空格
System.out.println(str3 + "===");
str3 = str1.replace("is", "was"); //替换字符串
System.out.println(str3);
String[] arr = str1.split(" "); //按指定的符号拆分字符串为数组
System.out.println(Arrays.toString(arr));
arr = str2.split(""); //用空字符串做split, 相当于返回字符串的字符数组
System.out.println(Arrays.toString(arr));
Math
-
Math.random() 产生0到1随机数(小数)
-
Math.floor() 取小数的最大整数(向下取整)
-
Math.ceil() 取小数的最小整数(向上取整)
-
Math.round() 四舍五入取整
Date
- 日期对象Date
- 日期格式化对象SimpleDateFormat
Date date = new Date(); //创建一个当前日期时间的日期对象
System.out.println(date);
/*
y 年, yyyy是四位的年份
M 月份。 跟后面的分钟区分,月的M是大写
d 日
H 大写的H表示24小时制的小时。 小写的h表示12小时制的小时
m 分钟
s 秒
日期(-)和时间(:)之间的连接符号可以随便定义
*/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = sdf.format(date);
System.out.println(format);
抽象关键字 abstract
抽象的作用是如果子类都会重写(不一样)的方法,定义为抽象方法,节省代码量。
抽象类里面也可以非抽象的方法,所有子类的代码都一样的方法就在父类中定义,代码重用。
abstract可用于修饰方法和类
抽象方法或类的特性
-
abstract用于修饰方法,该方法是抽象方法;如果用于修饰类, 该类就是抽象类
-
一个类如果有抽象的方法,类必须要定义为抽象类
-
抽象类不能直接被实例化
-
如果继承抽象类,必须要重新(实现 implements)父类中的所有抽象方法 ;否则这个子类也只能定义成抽象类
-
抽象类里可以有非抽象的方法
-
抽象类可以有构造方法,子类实例化时会调父类的构造方法
-
构造方法不能用abstract修饰
-
静态方法不能用abstract修饰
动物的抽象类,有一个抽象方法shout()
package com.hqyj;
public abstract class Animal {
public Animal() {
System.out.println("Animal的构造方法");
}
public abstract void shout();
public void breathe(){
System.out.println("动物在呼吸");
}
}
动物类的子类Cat, 重新父类的抽象方法
package com.hqyj;
public class Cat extends Animal{
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
抽象的面试题
-
抽象类中有构造方法吗?
有, 抽象类的构造方法在子类中可以调用
-
执行构造方法的时候,一定会创建出实例对象?
不一定。 子类创建对象时,如果默认构造方法,父类(不管是抽象还是具体类)的默认构造方法也会被调用
接口 interface
接口中的方法都是未实现的方法,可以理解为全是抽象方法。
接口一般定义规范(方法名,参数,返回类型),它的实现类必须按接口的方法的定义去实现方法
接口还用于框架或设计模式中,利用多态的特性,便于程序的扩展
接口可以实现多接口,弥补类的继承只能单继承缺点
- 定义接口的语法:
[修饰符] interface 接口名 extends 接口1,接口2{
}
- 接口实现的语法:
[修饰符] [abstract] class 类名 implements 接口1, 接口2{
}
接口的实现类可以是抽象类,如果是抽象类就不要求实现每一个方法;具体类就必须实现所有方法
Usb interface
package com.hqyj;
public interface Usb {
void init(); //初始化usb接口
void send(); //发送数据
void reject(); //弹出usb设备
}
实现类Usb2
package com.hqyj;
public class Usb2 implements Usb{
@Override
public void init() {
System.out.println("USB2.0 初始化");
}
@Override
public void send() {
System.out.println("USB2.0 传输数据");
}
@Override
public void reject() {
System.out.println("USB2.0 弹出设备");
}
}
实现类Usb3
package com.hqyj;
public class Usb3 implements Usb{
@Override
public void init() {
System.out.println("USB 3.0 初始化");
}
@Override
public void send() {
System.out.println("USB 3.0 传输数据");
}
@Override
public void reject() {
System.out.println("USB 3.0 弹出设备");
}
}
接口特点
- 接口定义的关键字是interface, 接口实现的关键字是implements
- 接口中的所有方法都只定义,不实现
- 接口的方法不写访问修饰符,默认是public
- 接口的属性默认是public static final的,相当于是常量
- 接口不能被实例化
- 接口没有构造方法
- 接口可以继承接口,并且可以多继承
public interface Usb {
//接口的属性默认是public static final, 接口中的属性就是常量
//等效于 public static final String name = "usb01";
String name = "usb01";
一个Java文件中定义多个类
-
只能有一个public类
-
其它的非public类只能默认修饰符,可以有多个非public类
-
在public类里是可以访问非public的类(因为在同一个包下)
package com.hqyj;
//一个源文件中只能有一个public类,类名跟文件名相同
public class A {
public static void main(String[] args) {
B b = new B();
b.btest();
}
}
//一个源文件中还可以有其它的非public(只能是默认修饰符)的类
class B{
public void btest(){
System.out.println("B类的方法");
}
}
class D{
}
内部类
定义在类中的类称为内部类
作用是封装,一般自己使用的类定义为内部类,不让别的类知道或使用这个内部类
成员内部类(内部类定义在大括号里)
-
内部类定义为一个成员(跟成员变量或方法同一级)
-
内部类可以用任意访问修饰符(一般起封装作用的话用private)
-
内部类可以访问外部类的属性或方法;外部类不能直接访问内部类的属性或方法,只能通过对象去访问
-
内部类的初始化要借助外部类的对象:Outer.Inner inner = outer.new Inner();
package com.hqyj;
public class Outer {
private String id = "abcd123";
private void funOuter(){
System.out.println("外部类的方法funOuter");
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.funOuter();
Outer.Inner inner = outer.new Inner();
inner.funInner();
System.out.println(inner.name);
}
private class Inner{
public Inner() {
System.out.println("Inner的构造方法");
}
private String name = "innerclass";
private void funInner(){
System.out.println(id);
System.out.println("内部类的方法funInner");
funOuter();
}
}
}
静态成员内部类
- 如果成员内部类是静态的,创建内部对象的方式: Inner inner = new OuterStatic.Inner();
- 内部类中只能访问外部类的静态属性和方法
- 在外部类中可以通过内部类的类名直接访问内部类的静态属性和方法
package com.hqyj;
public class OuterStatic {
private String nameOuter = "外部类的属性";
private static String nameOuterStatic = "外部类的静态属性";
private void funOuter(){
System.out.println("外部类的方法");
// System.out.println(nameInner);//不能访问内部类的属性
//funInner(); //不能访问内部类的方法
}
private static void funStaticOuter(){
System.out.println("外部类的静态方法");
System.out.println(Inner.nameStaticInner);
Inner.funStaticInner();
}
private static class Inner{
private String nameInner = "内部类的属性";
private static String nameStaticInner = "内部类的静态属性";
private void funInner(){
System.out.println("内部类的方法");
System.out.println(nameOuterStatic);
funStaticOuter();
}
private static void funStaticInner(){
System.out.println("内部类的静态方法");
System.out.println(nameOuterStatic);
funStaticOuter();
}
}
public static void main(String[] args) {
OuterStatic outer = new OuterStatic();
outer.funOuter();
Inner inner = new OuterStatic.Inner();
inner.funInner();
System.out.println(Inner.nameStaticInner);
OuterStatic.Inner.funStaticInner();
}
}
局部内部类(定义在方法中)
- 内部类定义在方法中
- 内部类的对象创建只能在这个方法中,创建内部类的方式:Inner inner = new Inner();
- 内部类可以访问外部类的属性和方法; 外部类不可以直接访问内部类的属性和方法,但是可以通过内部类的对象去访问
package com.hqyj;
public class OuterLocal {
private String nameOuter = "外部类的属性";
private void fun2Outer(){
}
private void funOuter(){
System.out.println("外部类的方法");
class Inner{
private String nameInner = "内部类的属性";
private void funInner(){
System.out.println("内部类的方法");
//内部类可以访问外部类的属性
System.out.println(nameOuter);
//内部类可以访问外部类的方法
fun2Outer();
}
}
// System.out.println(nameInner); //外部类不能直接访问内部类的属性
// funInner(); //外部类不能直接访问内部类的方法
Inner inner = new Inner();
inner.funInner();
System.out.println(inner.nameInner);
}
public static void main(String[] args) {
OuterLocal outer = new OuterLocal();
outer.funOuter();
}
}
匿名内部类
类只用一次,所以没有类名
一般用在接口作为方法的参数的情况
- Usb接口
package com.hqyj;
public interface Usb {
void work();
}
- Computer类
有一个方法的参数是Usb接口类型
package com.hqyj;
public class Computer {
//方法的参数是一个接口
public void useUsb(Usb usb){
usb.work();
}
}
- 调用接口参数的方法时,直接实现接口
这个接口的实现类没有名字,所以叫匿名类, 其它地方不可能用到它
package com.hqyj;
public class ComputerTest {
public static void main(String[] args) {
Computer computer = new Computer();
computer.useUsb(new Usb() {
@Override
public void work() {
System.out.println("鼠标正在工作...");
}
});
}
}
枚举 Enum
- 枚举定义
package com.hqyj;
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
- 枚举的使用
package com.hqyj;
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.AUTUMN;
switch (spring){
case SPRING:
System.out.println("春天播种");
break;
case SUMMER:
System.out.println("夏天施肥");
break;
case AUTUMN:
System.out.println("秋天收获");
break;
case WINTER:
System.out.println("收藏种子");
break;
}
}
}
类的方法
方法就是类的行为,通过方法执行一段代码,完成特定的功能
方法的定义:
[public] [static] [abstract] [fianl] 返回类型 方法名(类型 参数名, 类型 参数名){
//方法体代码
}
方法的特性:
-
除构造方法外,所有的方法都有返回类型,如果不需要返回值,返回类型是void
-
如果有返回值,方法必须有return语句(保证每一个分支都有return 语句),return语句后不能再有其它代码
-
方法可以无参数,有参数,还可以是不定长的参数
-
可变长参数放在方法参数的最后一个, 类型后面加3个点, 可变长参数在方法中当数组处理
-
方法同名,参数不同,就构成重载。可变长参数方法和固定长度参数的方法同时存在,优先调用定长参数的方法
public void fun1(){
System.out.println("没有返回值,返回类型是void");
}
//有返回类型,方法体必须有return语句,返回的内容必须跟返回类型一致
// 保证每一个分支都有返回语句
public String getName(){
String name = "";
if( 3 > 2){
name = "姓名";
}else {
}
return name;
/*
if( 3 > 2){
return "姓名";
}else {
return "";
}*/
}
//固定个数参数
public int sum(int a, int b){
System.out.println("a,b");
return a + b;
}
//类型后面有三个点参数叫可变长参数
// 可变长的参数只能是方法的最后一个参数
// 可变参数可以当数组处理
public int sum(int a, int... b){
System.out.println("a, b...");
int sum = 0;
sum += a;
for (int tmp : b) {
sum += tmp;
}
return sum;
}
public static void main(String[] args) {
MethodDemo methodDemo = new MethodDemo();
System.out.println(methodDemo.sum(1, 2, 3, 4, 5, 6));
}
传值调用和传引用调用
形参:形式参数,定义方法时给出参数名
//a, b就叫形参
public int sum(int a, int b){
}
实参:实际参数,调用方法时传的变量
int m = 3, n = 2;
//m,n 就叫实参
sum(m, n);
- 传值调用
当参数类型为基本类型时,在方法里面改变形参的值,不影响实参(理解为调用方法时copy了一份参数)
//定义方法
public int sum(int a, int b){
int sum = a + b;
a++;
b--;
return sum;
}
//调用方法
int x = 10;
int y = 20;
int sum = methodDemo.sum(x, y);
System.out.println(sum);
System.out.println("x:" + x + ", y:" + y); //x:10, y:20
- 传引用调用
当参数类型为引用类型(类,接口,数组),在方法里面改变这个对象的值,实参的对象的值也跟着改变(因为它们指向的是堆中的同一个对象实例)
Student实体类
package com.hqyj;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
package com.hqyj;
public class StudentTest {
public static void main(String[] args) {
StudentTest test = new StudentTest();
Student student = new Student("张三", 22);
test.updateStudent(student);
System.out.println(student);
}
public void updateStudent(Student stu){
stu.setAge(25);
}
}
方法的递归调用
方法里面调用自己,替代循环代码
(理解递归调用,知道方法被调用会在栈里压入一个方法帧,直到最后一次调用方法结束,再依次出栈,返回)
// 求1~n的和的递归方法
public int sumone2n(int n){
if(n == 1){
return 1;
}else {
return n + sumone2n(n - 1);
}
}
// 求n的阶乘的递归方法
public int factorial(int n){
if(n == 0){
return 1;
}else{
return n * factorial(n - 1);
}
}
// 斐波那契数列用递归
public long fibonacci(int n){
if(n == 1 || n == 2){
return 1;
}else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
sum(m, n);
+ 传值调用
当参数类型为基本类型时,在方法里面改变形参的值,不影响实参(理解为调用方法时copy了一份参数)
```java
//定义方法
public int sum(int a, int b){
int sum = a + b;
a++;
b--;
return sum;
}
//调用方法
int x = 10;
int y = 20;
int sum = methodDemo.sum(x, y);
System.out.println(sum);
System.out.println("x:" + x + ", y:" + y); //x:10, y:20
- 传引用调用
当参数类型为引用类型(类,接口,数组),在方法里面改变这个对象的值,实参的对象的值也跟着改变(因为它们指向的是堆中的同一个对象实例)
Student实体类
package com.hqyj;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
package com.hqyj;
public class StudentTest {
public static void main(String[] args) {
StudentTest test = new StudentTest();
Student student = new Student("张三", 22);
test.updateStudent(student);
System.out.println(student);
}
public void updateStudent(Student stu){
stu.setAge(25);
}
}
方法的递归调用
方法里面调用自己,替代循环代码
(理解递归调用,知道方法被调用会在栈里压入一个方法帧,直到最后一次调用方法结束,再依次出栈,返回)
// 求1~n的和的递归方法
public int sumone2n(int n){
if(n == 1){
return 1;
}else {
return n + sumone2n(n - 1);
}
}
// 求n的阶乘的递归方法
public int factorial(int n){
if(n == 0){
return 1;
}else{
return n * factorial(n - 1);
}
}
// 斐波那契数列用递归
public long fibonacci(int n){
if(n == 1 || n == 2){
return 1;
}else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}