Java面向对象OOP
Java面向对象 OOP
Object Oriented Programming
要解决的问题
单独的变量解决,不利于数据的管理,把某个对象的信息拆解了,例如:存储一只猫的名字,年龄var等信息
如果用数组,数据类型体现不出来,而且变量名和内容的对应关系体现不出来,例如:int只能用string来存,只能通过下标来获取信息
不能体现猫的行为,比如,数组的结构无法加入方法(函数)的处理方式
总之,不利于数据的管理,效率低
类与对象的概述
一个程序就是一个世界,有很多事物:对象[属性,行为]
数据类型分为Java提供的数据类型和自定义的数据类型
类就是一个数据类型
对象:可以通过一个类来创建一个对象
实例化:就是具体化,具体到一只猫
类是抽象的,概念的,代表一类事务:人类,猫类
对象是具体的,实际的,代表一个具体事物,即,是实例
类是对象的模板,对象是类的一个个体,对应一个实例
从类到对象的不同说法
创建一个对象
实例化一个对象
把类实例化
总之:类实例化 -> 对象
面向对象快速入门
- 使用OOP面向对象解决
- 实例化一只猫[创建一只猫对象]
public class Cat {
public static void main(String[] args) {
// 如何实例化一个对象
// 1. new Catt(); 创建一只猫
// 2. Catt cat1 = new Catt(); 把创建的猫赋给cat1
Catt cat1 = new Catt();
cat1.name = "abc";
cat1.age = 3;
cat1.color = "white";
// 如何访问对象的属性
System.out.println("第一只猫的信息" + cat1.name + " " + cat1.age + " " + cat1.color);
}
}
class Catt {
String name;
int age;
String color;
}
对象的内存布局,对象的存在形式
- new后产生的对象在栈内(对象和数组都是引用类型),有一个指向堆中的地址
- 如果实例化的类中又有字符串等引用类型的变量,会有一个从堆中指向方法区的地址
属性
属性的概念
class Catt {
String name; // name,age,color即为成员变量
int age;
String color;
String[] master; //属性可以是基本数据类型,也可以是引用数据类型(对象,数组)
}
Catt cat = new Catt();
类中的变量即为属性,成员变量是用来表示属性的
成员变量 = 属性 = field
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象,数组)。
Catt类实例化后产生的cat实际上是对象引用,而不是真正的对象
真正的对象是class内部的属性:name,age,color,master
对象引用:实际上就是人的名字,小明,不是真正的属性,因为小明是个字符串名字,而不是一个人,引用相当于起了一个别名
对象:对象才是真正的人,对象的胳膊腿就是属性
属性的定义
1.属性的定义语法同变量:访问修饰符 + 属性范围 + 属性名;
- 访问修饰符:public protected 默认 private ->访问范围逐渐缩小
对象
对象的创建
先声明再创建
Cat cat; //先声明,声明的时候没有分配内存空间 cat = new Cat(); //再创建,分配内存空间,实例化
直接创建
Cat cat = new Cat(); // 类的类型就是实例化的类的类型
声明格式:定义变量的类 + 定义变量的名字
访问对象中的属性
基本语法:对象名.属性名
用 . 来取对象中的属性
Person a = new Person(); a.age = 10; a.name = "小明"; Person b; b = a; // b.age = 200; b.name = null; //将b在栈中的指向地址置为空,也就是说,原来a和b都指向堆中的同一个地址的指针,b断掉了 System.out.println(a.age); // 这个时候输出a.age是经过b.age修改之后的200 System.out.println(b.age); // 这个时候输出b.age会出现异常
成员方法
- 某些情况下,需要定义成员方法,简称方法
- 比如人类:除了由一些属性(年龄,姓名)以外,还要有一些行为,例如说话等
public class Method01 {
public static void main(String[] args){
//方法使用
//1. 方法写好后,不去显式调用不会使用方法
//2. 先创建对象,创建对象后和调用属性的方式相同调用方法
for(int i = 0;i < 100; i ++){
Person p1 = new Person();
p1.speak();
}
}
}
class Person {
String name;
int age;
// public 表示方法是公开的
// void 表示方法没有返回值
// {} 大括号内是方法体,嵌入执行代码
public void speak() {
System.out.println("abc");
}
}
public class Method02 {
public static void main(String[] args){
Person p1 = new Person();
p1.cal01();
p1.cal02(5); //调用cal02方法,并且将5传给cal02fan
}
}
class Person {
public void cal01()
{
int res = 0;
for (int i = 1; i < 1000; i++) {
res += i;
}
System.out.println("计算结果" + res);
}
public void cal02(int n){ // (int n) 形参列表,表示当前有一个形参n,可以接受用户输入
int res = 0;
for(int i = 0;i <= n;i++){
res += i;
}
System.out.println("res的结果为" + res);
}
}
import java.util.Scanner;
public class Method02 {
public static void main(String[] args){
Person p1 = new Person();
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
p1.cal01();
p1.cal02(n); //调用cal02方法,并且将5传给cal02方法
}
}
class Person {
public void cal01()
{
int res = 0;
for (int i = 1; i < 1000; i++) {
res += i;
}
System.out.println("计算结果" + res);
}
public void cal02(int n){ // (int n) 形参列表,表示当前有一个形参n,可以接受用户输入
int res = 0;
for(int i = 0;i <= n;i++){
res += i;
}
System.out.println("res的结果为" + res);
}
}
public int getSum(int num1,int num2){ //(int num1.int num2)形参列表,2个形参,可以接受用户传入的两个数
int res = num1 + num2;
return res; //表示把res的值,返回
}
方法的妙用
- 避免冗余度太高:当需要多次调用时,可以放到外面多次调用,提高代码的复用性
public class Method03 {
public static void main(String[] args) {
Tools tool = new Tools();
int[][] map = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int res = tool.getSum(map);
System.out.println(res);
}
}
class Tools {
public int getSum(int[][] map) {
int res = 0;
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
res += map[i][j];
}
}
return res;
}
}
- 可以将实现的细节封装起来,然后供其它用户来调用即可
成员方法的定义
访问修饰符[public private protected] 返回数据类型[void int ...] 方法名 (形参列表){
//方法体语句
return 返回值;
}
- 如果不写,为默认访问修饰符,default
返回数据类型
一个方法最多返回一个值
如果想要返回多个结果,则需要返回一个数组,将结果封装到一个数组中去 (即将返回值设为int[]等数组类型)
public class MethodDetail { public static void main(String[] args) { AA a = new AA(); int[] res = a.getSumAndSub(1, 2); System.out.println("sum=" + res[0]); System.out.println("sub=" + res[1]); } } class AA{ public int[] getSumAndSub(int num1,int num2){ int[] res = new int[2]; res[0] = num1 + num2; res[1] = num1 - num2; return res;//这个地方res是一个数组,所以应该返回一个地址 } }
返回类型可以为任意类型,包括基本数据类型和引用类型(数组,对象)
返回值类型必须和return的值类型一致或兼容
void的返回值可以写return,此时return后面不能带任何东西,默认return值为空
注意,方法体里面不能再定义方法
- 方法体里面不能再定义方法,不能嵌套应用(注意这个地方是不能定义,而不是不能使用,函数中可以使用函数,递归)
方法调用的细节
同一个类中的方法调用,可以直接调用
public class Method03 { public static void main(String[] args) { A a = new A(); a.sayOk(); } } class A { public void print(int n) { System.out.println("print方法被调用" + n); } public void sayOk() { print(10); } }
跨类中的方法A类调用B类方法:需要通过对象名调用(即在A类中再实例化一个变量B)
public class Method03 { public static void main(String[] args) { A a = new A(); a.sayOk(); a.m1(); } } class A { public void print(int n) { System.out.println("print方法被调用" + n); } public void sayOk() { print(10); //这个地方没有使用.去对象名调用,因为这个函数的定义在同一个类中 } public void m1(){ System.out.println("m1方法被调用"); B b = new B(); b.hi(); //这个地方进行了对象名调用,因为方法的定义不在同一个类中 } } class B{ public void hi(){ System.out.println("B类中的hi()被执行"); } }
重载OverLoad
- java中允许同一个类中,多个同名方法的存在,但是要求形参列表不一致
- 重载的好处:1. 减轻了起名的麻烦 2. 减轻了记名的麻烦
实例
```java
public class Caculation01 {public static void main(String[] args) { Mycaculator m1 = new Mycaculator(); System.out.println(m1.calculate(1.1, 2.2)); System.out.println(m1.calculate(1, 2)); }
}
class Mycaculator {
public int calculate(int n1, int n2) { // 如果只是参数名不同则不能构成重载 return n1 + n2; } public double calculate(int n1, double n2) { return n1 + n2; } public double calculate(double n1, double n2) { return n1 + n2; }
}
3. 参数顺序不一样也是重载,先int后double 和 先double后int不同 ### 方法重载的细节 1. 方法名必须相同 2. 参数列表:必须不同(形参**类型**或**个数**或**顺序**,**至少有一样不同**,**参数名无要求**) 3. **返回类型不要求**(如果只是返回类型不同而其它都相同,则不构成重载) 4. 能否构成重载,就看**在编译的时候是否报错**即可,如果报**已经定义了这个方法**的错误,就不构成重载 ## 可变参数 1. java允许将同一个类中多个**同名同功能**但是参数个数不同的方法,封装成一个方法 2. ```java public class Var{ public static void main(String[] args){ HspMethod m1 = new HspMethod(); System.out.println(m1.sum()); } } class HspMethod{ public int sum(int... nums){ //1. ...表示接受的是可变参数,类型是int,即可以接受多个int(0-多),int... nums,int... nums //2. 使用可变参数时,可以当作数组来使用,即nums可以当作数组,即nums可以取下标,当作数组去处理 //3. 遍历nums求和即可 int res = 0; for(int i = 0;i < nums.length;i++){ //因为可变参数的参数类型可以当作数组去处理,所以可以取length res += nums[i]; } return res; } }
上面的可以通过重载来实现(函数名相同,但是参数不同)
也可以使用可变参数:方法名称相同,功能相同,参数个数不同 -> 使用可变参数优化
基本语法
访问修饰符 返回类型 方法名(数据类型... 形参名){}
- 注意这个地方数据类型后面的三个点是不能够更改的,是表明参数的个数是多个,省略号
- 数据类型和后面的三个点…是不能够分隔的
注意事项
- 可变参数的实参可以是0个或任意多个
- 可变参数的实参也可以直接是数组
- 可变参数可以和普通类型的参数一起放在形参列表,但是必须保证可变参数在最后,否则编译都会报错,更别提使用了
- 一个形参列表中只能有一个可变参数,并且需要放到参数列表的最后
构造方法
需求
- 前面的都是先创建好一个对象之后,再给他的年龄赋值
- 现在要求:在创建人类的对象时,直接指定这个对象的年龄和姓名,一次完成
- 构造器的主要目的:完成对新对象的初始化,而不是创建新对象
基本语法
[修饰符] 方法名(形参列表){
方法体
}
- 构造器的修饰符是可以默认的
- 构造器没有返回值,只有构造器可以没有返回值,没有返回值的只有构造器
- 方法名必须和类名一样
- 参数列表和成员方法一样的规则
- 构造器的调用,由系统完成
- 一个类可以定义多个不同的构造器,即构造器重载,当构成重载时,未指定的参数将会赋值为默认值
- 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器,比如Dog(){},使用javap指令反编译可以查看源代码
- 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的构造器,除非显式地声明一下,和自己定义的构成重载
实例
public class Test02 {
public static void main(String[] args) {
// 当new一个对象时,直接通过构造器指定名字和年龄
// 在new的时候直接传参,可以使用构造器
Person p1 = new Person("smith", 80);
// 构造器在被调用的时候,对象应该已经被创建了
}
}
class Person {
String name;
int age;
public Person(String pname, int page) {
System.out.println("yes");
name = pname;
age = page;
}
}
- 构造器在被调用的时候,对象已经被创建了,是通过new 类(),括号进行传参
Javap的使用
javap时jdk提供的一个命令行工具,Javap能对给定的class文件提供字节代码进行反编译
通过它,可以查看到源代码
格式:
javap xxx.class 或者 javap xxx (可以不带.class)
-c 对代码进行反汇编
-v 输出附加信息
-p 显式所有类和成员
IDEA
Eclipse
IDEA使用技巧
更改编码
源码一定要放到src目录下
新建一个类时,new java class的时候,不需要带上.java的文件名后缀
只需要写清类名,后面自动填上.java
IDEA常用快捷键
-
快速格式化的快捷键:ctrl+alt+L,可以快速补齐代码
模板快捷键
- 模板可以高效地完成开发,提高效率
包
应用场景
作用
- 可以区分相同名字的类
- 当类很多时,可以很好地管理类(某个类在某个包中,好搜索)
包的基本语法
package com.hspedu; package 包的名字;
- package 关键字,表示打包
- com.hspedu 表示包名
包的原理
- 包的本质就是创建不同的文件夹来保存文件
快速入门
- 打包:选中src -> new -> Package
- 点号操作符 . 是用来向下继续引目录的,例如在src下创建包com.abc,是src下有com,com下有abc
- 当有一个相同类名被实例化时,想要实例化另一种类,那么需要用 . 操作符来标识是哪个包(就是加上包名)
- 如果引用两个相同名字的类,编译器会报错,不能同时引两个Dog
包的命名规则
- 只能包含数字,字母,下划线,小圆点,但是不能用数字开头,不能是关键字或保留字
- 即不能包含class等关键字
- 一般是:小写字母 + 小圆点
- 一般是 com.公司名.项目名.业务模块名,最后一级一般是类的名字
导入包
- 语法:import 包;
- 不要忘记import最后的分号
- 引入的主要目的是:使用该包下面的类
- 和python的import相同,可以只导入包中的某个类,也可以使用*去导入所有的包
- 需要使用哪个类就引入哪个类,不一次全部引入某个包中的所有类
- package的作用是声明当前类所在的包,需要放在类的最上面(一个文件的最上面),一个类中最多只有一句package
- 一个类中最多只有一句package,即一个类只能属于一个包
- import指令位置放在package的下面,在类定义的前面,可以有多句且没有顺序的要求
修饰符
- 类似于一个阶梯图形
- 同类就是本类
封装
- 封装就是把抽象出的数据**[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法]**,才能对数据进行操作
好处
封装的实现步骤,三步走
将属性私有化 private 不能直接修改属性
提供一个公共的set方法,用于对属性进行判断并且赋值
public void setXxx(类型 参数名){ //加入数据验证的业务逻辑 如果通过则 属性=参数名 }
提供要给公共的get方法,用于获取属性的值
public 数据类型 getXxx(){ //权限判断 }
自己写set和get太慢,可以使用快捷键,alt + insert,getter and setter
注意事项
set方法返回值为void,不能再写一个变量,赋值给它
并且p1的age是private类型的,不能由其它类访问,所以p1.age本身就是错误的
上面就是set方法,又去赋值,是错误的
在调用set方法时,只需要去传参即可
this关键字
目的
构造器的形参,如果能够直接写成属性名
public Dog(String name,int age){ name = name; age = age; }
但是出现了一个问题,根据变量的作用域原则,构造器的name是局部变量,而不是属性,构造器的age也是局部变量,而不是属性
这样做相当于是name局部变量又赋值给了它本身,没有用处,当这个构造器结束时,局部变量的作用域一结束,就什么都没了
什么是this
- this代表当前对象
- 形象说法:老韩说我的,是指老韩;小明说我的,是指小明,不同的对象指向不同的this
public Dog(String name,int age){
//所以上面的name和age应该加上this,this.age就是当前对象的属性age
this.name = name;
this.age = age;
}
this小结
- 简单地说:哪个对象调用,this就代表哪个对象
命名问题
- 担心命名时使用了关键字,可以在名字后面加上下划线__
继承
为什么需要继承
- 编写两个类,一个大学生,一个大学毕业生,发现两个类的属性和方法有很多是相同的
- 继承提高代码复用性
快速入门
当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可
父类中包含子类中所有属性和方法,子类中可以包含不同的特有属性
可以继续继承,下一层会包含上一层的所有属性
我的理解:extends相当于复制了父类中的所有属性和方法
继承细节
第一:私有属性不能直接在子类中访问
- 子类继承了所有的属性和方法,但是私有属性不能在子类直接访问,要通过公共的方法去访问
- 访问父类中的private属性,可以写一个getter,return值写private的值,和封装的原理相同
第二:父类和子类的构造器调用问题
- 子类必须调用父类的构造器,完成父类的初始化
- 也就是说,在main函数中实例化子类时,是首先进行父类的构造器调用,然后再进行子类的构造器调用(初始化)
第三:在父类中没有默认的无参构造器时
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不会通过
第四:指定调用父类的某个构造器
指定地去调用父类的某个构造器,则需要显式的调用
```java
super(参数列表)3. 调用父类中的**无参构造器**,super()中的参数什么都不写,默认调用super() #### 第五:super的使用 1. super在使用时,**必须放在构造器的第一行** 2. super关键字**只能在构造器中使用,不能在普通成员方法中使用** #### 第六: 1. super()和this()都只能放在构造器的第一行,因此这两个方法不能共存在一个构造器 #### 第七:object类 1. Java所有类都是Object的子类,Object是所有类的基类 2. 按住 **ctrl + h** 可以看到所有类的父子关系 3. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220822171532.png) 4. 如果new了最后一个子类,会**一级一级地调用上一级的构造器**,直到**调用到了顶级父类的构造器**,**Object类** 5. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220822171916.png) #### 第九:单继承 1. 子类最多**只能继承一个父类**,即Java中式**单继承机制** 2. 如果想让A继承B和C,则可以让**A继承B,B继承C** #### 第十:不能滥用继承 1. 不能滥用继承,子类和父类之间必须满足 **is-a** 的逻辑关系 2. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220822172523.png) ## super ### 概念 1. super代表父类的引用,用于访问父类的属性,方法,构造器 ### 具体用法 1. 访问父类的属性,但是不能访问父类的private属性,可以 ```java super.属性名
访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
调用父类的构造器(因为只有一个父类,所以不需要加父类的名字就可以区分),当参数不同时,调用语法:
super(参数);
方法的重写 override
概念
- 方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称,返回类型,参数,都一样,那么就说子类的这个方法覆盖了父类的那个方法
实例
package Override;
public class Override01 {
public void cry(){
System.out.print("动物叫唤");
}
}
//父类
package Override;
public class Dog extends Override01 {
public void cry() {
System.out.print("小狗汪汪叫");
}
}
//子类
- 因为Dog是Animal子类
- Dog的cry方法和Animal的cry定义形式一样(名称,返回类型,参数)
- 这时,我们就说Dog的cry方法,重写了Animal的cry方法
方法重写细节
方法重写也叫方法覆盖,需要满足下面的条件
子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样,否则不构成重写
子类方法的返回类型和父类一样,或者,是父类返回类型的子类,比如父类返回类型是Object,子类方法返回类型是String
```java
public Object cry() { return null; }//父类 public String cry(){ return null; }//子类
5. 否则会报错![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220822181100.png) 6. 子类方法不能缩小父类方法的访问权限 public -> protected -> 默认 -> private 7. 也就是说,返回类型可以细化,但是访问范围不能缩小 8. 例如:父类中为public , 子类中为private,则会报错 9. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220822181730.png) ## 多态polymorphic ### 解决的问题 1. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220822222119.png) ```java package A; public class Master { private String name; public Master(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } //给小狗吃骨头 public void feed(Dog dog,Bone bone){ //这个地方的参数,和int a一样,只是声明了数据类型和变量名 System.out.print("主人" + name + "给" + dog.getName() + "吃" + bone.getName()); } //给小猫吃🐟,和给狗吃骨头构成重载 public void feed(Cat cat,Fish fish){ System.out.print("主人" + name + "给" + cat.getName() + "吃" + fish.getName()); } //如果有很多动物和食物,会导致feed重载量很大 }
package A;
public class Poly01 {
private String name;
public Poly01(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
}
class Fish extends Poly01{
public Fish(String name){
super(name);
}
}
class Bone extends Poly01{
public Bone(String name){
super(name);
}
}
package A;
public class Animal {
private String name;
public Animal(String name){
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
- 如果动物很多,食物很多,会导致feed方法很多(重载很多方法),不利于管理和维护
- 但是本质就是给某个动物喂某种食物
- 多态可以降低代码复用性
多态的基本介绍
- 多:多种
- 态:状态
- 方法或对象具有多种形态,是面向对象的第三大特征
- 多态是继承在封装和继承基础之上的
方法的多态
- 重写和重载就体现多态
- 重载传入不同的参数,就会调用不同的方法,就体现了多态
- 重写调用不同的对象,不同对象的方法为重写,体现了多态
对象的多态(核心)
重要的几句话(背过)
一个对象的编译类型和运行类型可以不一致
一个父类的引用可以指向一个子类的引用
编译类型在定义对象时,就确定了,不能改变
运行类型是可以变化的
编译类型看定义时 = 的左边,运行类型看 = 的右边
体验对象多态的特点,编译类型和运行类型
编译时,javac,类型由声明该变量时使用的类型决定
运行时,java,类型由实际赋给该变量的对象决定
```java
Person p = new Women(); //Women继承自Person类4. 上面的p会得到Person类的属性,调用Women类的方法 5. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220823001726.png) 6. 可以理解为:编译类型决定了变量的属性,而运行类型决定了方法 ### 使用多态机制,可以统一的管理主人喂食的问题 ## 抽象方法 ### 处理问题: 1. 处理的主要问题是:父类方法的不确定性问题 2. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220826211645.png) 3. 考虑将该方法设计为抽象(abstract)方法 4. 所谓抽象方法就是没有实现的方法,所谓没有实现,就是没有方法体 5. 当遇到 6. ```java Missing method body , or declare abstract
时,说明如果没有方法体,可以声明为一个抽象方法
抽象方法只需要声明即可
```java
public abstract void eat();
限制范围 abstract 返回值类型 函数名();10. 将一个类做成抽象类,并让子类去具体实现,父类只是起到一个声明的作用 11. 一般来说,抽象类会被继承,由其子类来继承 ### 抽象类细节 1. 用abstract关键字来修饰一个类时,这个类就叫做抽象类 2. ```java 访问修饰符 abstract 类名{ //属性 //方法 public abstract void eat(){} }
用abstract关键字来修饰一个方法时,这个方法就是抽象方法,
```java
访问修饰符 abstract 返回类型 方法名(参数列表); //后面不能加方法体,否则会报abstract methods cannot have a body5. 抽象类的**价值更多作用在于设计**,是设计者设计好后,让**子类继承并实现** 6. 后面的**接口**是很常见的抽象类调用 7. 抽象类不能被实例化 8. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220826212813.png) 9. **抽象类不一定要包含abstract方法**,也就是说,抽象类可以没有abstract方法。抽象类中还可以有实现的方法 10. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220826213103.png) 11. **一旦一个类包含了abstract方法,则这个类必须声明为abstract**(但是一个abstract类中不一定包含abstract方法) 12. **抽象类可以有任意成员** [抽象类的**本质还是类**] ,比如:非抽象方法,构造器,静态属性等等 13. 抽象方法不能有主体,即不能实现 14. 如果**一个类继承了抽象类**,则它**必须实现抽象类的所有抽象方法**,**除非它自己也声明为abstract类** 15. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220826214526.png) ### abstract修饰的对象 1. abstract只能修饰类和方法 2. abstract不能修饰属性和其它,抽象的属性说不通 3. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220826213451.png) ## 接口interface ### 快速入门 1. usb插槽就是现实的接口 2. 不用担心哪个插槽是插哪个的,因为做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定,包括尺寸,排线等等 3. 程序就是一个世界 ### 实现思路 1. 实现接口,就是把接口方法实现 2. 接口方法就是一个标准,类似于USB2.0和USB3.0 ### 概念 1. **接口就是给出一些没有实现的方法**,封装到一起,到某个类**要使用的时候**,再根据**具体情况把这些方法写出来** 2. ```java 访问范围修饰符 interface 接口名{ //属性 //方法,这个时候方法只有方法名,没有方法体的具体实现 } class 类名 implements 接口{ //自己属性 //自己方法,这个时候可以根据具体情况加上类的调用 //必须实现的接口的抽象方法 }
在接口中,抽象方法,可以省略abstract关键字
implement关键字用来调用接口,生效,执行,实施
在jdk8后,可以有默认实现方法,需要使用default关键字修饰
default public void ok(){ }
在jdk7.0前,接口里所有方法都没有方法体,即都是抽象方法
在jdk8.0以后,接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
接口的应用场景
- 当不同的程序员在开发时,如果写了不同的方法,综合的人调用起来就会很麻烦
- 所以接口的目的就是指定一个标准,接口只去定义后来的方法名,而不去定义具体方法
- 根据接口写类的程序员根据接口,再去写具体的方法,但是方法名不会有区别
- 最后,整合的程序员实例化对象,并且通过这个实例化出来的对象,调用这个对象的成员方法
接口的注意事项
- 接口不能被实例化,所以new interface_name是错误的
- 接口中所有的方法都是public方法,所以可以不写public关键字,默认的就是public(目的就是能够让所有的编写类的程序员都能够访问到接口)
- 接口中的抽象方法,可以不用abstract来修饰,下图可以看到,public和abstract都是灰色的,即可有可无的
- 一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alt+enter的快捷键去快捷调用所有接口中的方法
- 因为在继承时,子类不能缩小父类的访问范围,所以因为接口(父类)访问范围是public,子类的访问范围也就是public,如果访问范围限制为default或更小,则会报错
- 抽象类实现接口,可以不用实现接口的方法
类变量
待解决问题
快速入门
定义所有对象共享的变量即为类变量
```java
类名.变量名 Child.count
或者也可以
实例化的对象.变量名 child1.count4. 可以**直接由类名访问**,因为是类变量 5. 也可以由**实例化的对象进行类变量的访问**,因为这几个对象出自一个类,所以 这几个对象的变量是**共享的** 6. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220828154057.png) ### 什么是类变量 1. **类变量也叫静态变量,静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的值都是相同的值,同样,任何一个该类的对象去修改它时,修改的也是同一个变量** 2. ```java 访问修饰符 static 数据类型 变量名;
访问类变量:类名.类变量名 或者 对象名.类变量名
类变量并不依赖于实例,类变量是随着类的加载而创建的,所以没有创建对象实例也可以访问
```java
public class lei {public static void main(String[] args){ //这个地方是通过 类名.变量名 去访问的,类变量 sta.a++; System.out.print(sta.a); }
}
class sta{
public static int a = 0;
}
### 类变量使用细节 #### 使用类变量的时候 1. 在需要**这个类的所有对象都能访问**到这个变量的时候就需要使用类变量(共享一个变量) 2. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20220828160025.png) #### 类变量和实例变量的区别 1. 类变量是该类的**所有对象共享的**,而实例变量是**某个对象独享的** 2. 实例对象如果没有实例化则不能访问 3. 类变量如果没有实例化仍可以访问 #### 加上static称为类变量或者静态变量,否则称为实例变量、普通变量、非静态变量 #### 推荐使用 类名.类变量名 来访问 ### 类变量的生命周期 1. 类变量的声明周期是**随着类的加载开始,随着类的消亡而毁灭** 2. 只要类没有消亡,类变量就不会消亡 ## 类方法 ### 静态方法 1. ```java 访问修饰符 static 数据返回类型 方法名(){}
当方法使用了static修饰后,该方法就是静态方法
静态方法就可以访问静态属性/变量
main方法语法
解释main方法的形式
public static void main(String[] args){}
重点
- main方法是jvm虚拟机调用
- java虚拟机在执行main()方法时,不必创建对象,(类似于类方法,可以让这个类的所有对象都能访问到,共享),所以该方法必须是static,即在调用主类的main方法时,是没有必要去实例化这个主类的,只需要调用main方法(也没有见过实例化主类,为了调用main方法)
- java虚拟机需要调用类的main方法,所以该方法的访问权限必须是public
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- 可以对main函数中的args取下标,来获取到main函数的参数的具体的值
- 实际是将main函数的参数打包成一个字符串,然后传给main函数
- java 执行的程序 参数1 参数2 参数3
main的特别说明
- 在main方法中,可以直接调用main方法所在的类的静态方法或静态属性
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
- 静态方法,可以访问本类的静态成员
- 静态方法,不可以访问本类的非静态成员(static的类,不能访问非static的成员变量,即不能访问非static的属性和方法)
- 如果想要访问非静态成员,则必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
- 静态方法main,要访问本类的非静态成员,需要先创建对象,再调用即可
- 如果main方法和要调用的非静态成员在一个类中,则需要将这个main方法和非静态成员共存的class实例化
- 实例化前:
- 后:
- 访问方法和调用成员方法相同,使用 . 去访问
java数据结构
链表的实现
java ListNode链表是用Java自定义实现的链表结构
```java
class ListNode{int val; ListNode next;
}
4. 添加构造方法,方便初始化 ```java class ListNode{ int val; ListNode next; ListNode(int val){ this.val = val; } }
删除一个节点
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public void deleteNode(ListNode node) { ListNode p = node.next; node.val = p.val; node.next = p.next; } }
Java注解
- @Override 告诉编辑器这个方法是覆盖父类的方法,在子类继承父类的时候用到,重写的时候用到
- @WebServlet(“/test”) 表示这个类是一个Servlet,Web容器就会识别这个注解,在运行的时候调用它,涉及到在使用tomcat就会调用它
- @Controller(“/test”) 表示某个类是一个控制器,告诉Spring框架该类是一个控制器
注解和注释
- 注释和注解不同,注解会影响程序的运行,而注释编译器则不会对其操作
- 注释是给开发人员看的,注解不是给人看的,是用于给程序看的,会影响程序的编译和运行,编辑器,框架,tomcat
使用
- 自定义开发一个Web容器,基本功能是加载Servlet,需要管理它的生命周期,所以必须先识别程序中的哪些类是Servlet
- 程序启动的时候,扫描所有的类,找出添加了@WebServlet注解的类,进行加载
- @WebServlet是在程序运行的时候起作用的,Java就把它的作用范围规定为RUNTIME
- @Override是给编译器看的,编译器工作的时候识别出了包含@Override注解的方法,就去检查上层父类的相关方法,存在则通过,否则报错
开发常用
@RequestMapping
@RequestMapping可以将HTTP请求映射到某个类中的方法
@RequestMapping(value = "/ex/foos", method = RequestMethod.GET) @ResponseBody public String getFoosBySimplePath() { return "Get some Foos"; } //value值写路径,method写方法
详细参数看(100条消息) SpringMVC-@RequestMapping的参数和用法_流烟默的博客-CSDN博客_@requestmapping
最重要的参数:value=”url”,method= RequestMethod.POST/RequestMethod.GET