day6 类与对象,封装,构造方法
类与对象
- 面向对象 当需要实现一个功能的时候,不关心具体的步骤,而是找一个已经具有该功能的人,来帮我做事儿。
- 面向过程 当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为,详细处理每一个细节。
类
- 类 :是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。
- 类的属性与行为: 略。
- 类的定义:就是定义类的成员,包括成员变量和成员方法
- 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。
- 成员方法:和以前定义方法几乎是一样的。只不过把 static 去掉。
对象
- 对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性 和行为.
类的定义格式
- 定义类,就是定义类的成员,包括成员变量 与成员方法
- 成员变量:跟定义变量几乎一样,唯一不同的是位置发生了改变,在类中,方法外
- 成员方法: 和以前定义方法几乎是一样的,只是把static去掉
- 类的定义格式举例:
1 2 3 4 5 6 7 8 9 10
public class Student { String name; int age ; public void study(){ System.out.println("好好学习天天向上"); } public void eta(){ System.out.println("学习饿了要吃饭"); } }
对象的使用
- 通常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用。
- 导包:也就是指出需要使用的类,在什么位置。import 包名称.类名称;
- 创建,格式: 类名称 对象名 = new 类名称(); Student stu = new Student();
- 使用,分为两种情况: 使用成员变量:对象名.成员变量名 使用成员方法:对象名.成员方法名(参数)
举例说明如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
public class Demo02Student { public static void main(String[] args) { // 1. 导包。 // 我需要使用的Student类,和我自己Demo02Student位于同一个包下,所以省略导包语句不写 // 2. 创建,格式: // 类名称 对象名 = new 类名称(); // 根据Student类,创建了一个名为stu的对象 Student stu = new Student(); // 3. 使用其中的成员变量,格式: // 对象名.成员变量名 System.out.println(stu.name); // null System.out.println(stu.age); // 0 System.out.println("============="); // 改变对象当中的成员变量数值内容 // 将右侧的字符串,赋值交给stu对象当中的name成员变量 stu.name = "赵丽颖"; stu.age = 18; System.out.println(stu.name); // 赵丽颖 System.out.println(stu.age); // 18 System.out.println("============="); // 4. 使用对象的成员方法,格式: // 对象名.成员方法名() stu.eat(); stu.sleep(); stu.study(); } }
成员变量的默认值 成员变量跟数组一样有默认值,具体表格如下。
对象使用的内存图。
- 堆区
- 存储的全部是对象,每个对象都包含一个与之对应的 class 的信息。(class 的目的是得到操作指令)
- jvm 只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 。
- 栈区
- 每个线程包含一个栈区,栈中只保存基础数据类型的值和对象以及基础数据的引用
- 每个栈中的数据(基础数据类型和对象引用)都是私有的,其他栈不能访问。
- 栈分为 3 个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
- 方法区
- 又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的 class 和 static 变量。
- 方法区中包含的都是在整个程序中永远唯一的元素,如 class,static 变量。
- 程序执行基本流程
- 方法区包含所有的class文件,里面包含成员变量(实例变量) 以及成员方法。
- 找到程序入口main方法,main方法压栈进入栈区。
- 栈中的基础数据与对象引用对每个线程都是私有的。
- new 一个对象,对象存入堆中,栈中只存引用。
- 关于方法调用的内存图,栈区保存对象的引用地址,调用某个对象的方法,通过地址找到堆区中的实例对象,再通过对象找到方法区中的成员方法。
只有一个对象的内存图
两个对象使用同一个方法的内存图
两个引用只想同一个对象的内存图
使用对象类型作为方法的参数
使用对象类型作为方法的返回值
成员变量与局部变量的区别
1
2
3
4
5
6
7
public class Car{
String color; // 成员变量
public void drive(){
int speed = 80; // 局部变量
System.out.ptintln("时速"+speed);
}
}
- 在类中的位置不同
- 成员变量:类中,方法外
- 局部变量: 方法中
- 作用范围不一样
- 成员变量:类中
- 局部变量:方法中
- 初始化值不同
- 成员变量:有默认值
- 局部变量:没有默认值。必须先定义,赋值,最后使用
封装
- 原则:先将属性隐藏起来,若要访问某个属性,提供公共的方法对其访问
- 步骤
- 使用 private 关键字来修饰成员变量
- 对需要访问的成员对量,提供对应的 getXxx 方法和 setXxx 方法。
封装操作
以代码举例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Person {
String name; // 姓名
private int age; // 年龄
public void show() {
System.out.println("我叫:" + name + ",年龄:" + age);
}
// 这个成员方法,专门用于向age设置数据
public void setAge(int num) {
if (num < 100 && num >= 9) { // 如果是合理情况
age = num;
} else {
System.out.println("数据不合理!");
}
}
// 这个成员方法,专门私语获取age的数据
public int getAge() {
return age;
}
}
public class Demo03Person {
public static void main(String[] args) {
Person person = new Person();
person.show();
person.name = "xyx";
// person.age = -20; // 直接访问private内容,错误写法!
person.setAge(20);
person.show();
}
}
封装操作的优化 1 –this 关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public void sayHello(String name) { System.out.println(name + ",你好。我是" +this.name); System.out.println(this); } public static void main(String[] args) { Person person = new Person(); // 设置我自己的名字 person.name = "王健林"; person.sayHello("王思聪"); System.out.println(person); // 地址值 }
- 方法被哪个对象调用,方法中的 this 就代表那个对象。即谁在调用,this 就代表谁。
封装优化操作 2 – 构造方法
- 构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样
- 构造方法不要写返回值类型,连 void 都不写
- 构造方法不能 return 一个具体的返回值
- 如果没有编写任何构造方法,那么编译器将会默认赠送一个构造方法,没有参数、方法体什么事情都不做。 例: public Student() {}
- 一旦编写了至少一个构造方法,那么编译器将不再赠送。
- 构造方法也是可以进行重载的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student {
// 成员变量
private String name;
private int age;
// 无参数的构造方法
public Student() {
System.out.println("无参构造方法执行啦!");
}
// 全参数的构造方法
public Student(String name, int age) {
System.out.println("全参构造方法执行啦!");
this.name = name;
this.age = age;
}
}
标准代码 javabean
- 一个标准的类通常要拥有下面四个组成部分:
- 所有的成员变量都要使用 private 关键字修饰
- 为每一个成员变量编写一对儿 Getter/Setter 方法
- 编写一个无参数的构造方法
- 编写一个全参数的构造方 可使用 Alt + insert 快速创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Student {
private String name; // 姓名
private int age; // 年龄
public Student() {
}
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;
}
}
day7 Scanner 类、Random 类、ArrayList 类
API 简述
API(Application Programming Interface),应用程序编程接口。Java API 是一本程序员的 字典 ,是 JDK 中提供给我们使用的类的说明文档。这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。
screen 类 与 random 类
引用类型的一般步骤:
- 导包
import 包名.类名;
- 创建对象
数据类型变量名=new 数据类型(参数列表);
- 调用方法
变量名.方法名();
screen 的基本用法
1
2
3
4
5
6
7
8
9
import java.util.Scanner;
public class demo {
public stasitc void main (String[] args){
Screen sc = new Screen(System.in)
int num = sc.nextlnt ();// 获取数字
String str = sc.next(); // 获取字符串
}
}
random 的基本用法
1
2
3
import java.util.Random;
Random r = new Random();
int number = r.nextInt(10);
ArrayList 类
引入对象数组
使用学生数组,存储三个学生对象
1
2
3
4
public class Student {
}
Student[] students = new Student[3];
ArrarList 类
几点基础:
- 数组的长度不可改变,但 Arrylist 的长度是可以随意变化的
- 对于 Arrylist 来说,
代表泛型 - 泛型的意义:集合中的所有元素,全都是统一类型
- 泛型只能是引用型,不能是基本的数据类型
对与 Arrylist 来说,直接打印不是得到地址而是内容。
1 2 3 4 5 6 7 8 9
import java.util.ArrayList; ArrayList<String> list = new ArrayList<>(); list.add("xyx"); // 向列表中增加元素 String name = list.get(2); // 从集合中读取元素 String name-1 = list.remove(3); // 删除集合元素 int size = list.size(); // 取集合长度
如何存储基本类型
1 2 3
ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2);
day 8 String 类、static 关键字、Arrays 类、 Math 类
String 类
字符串的特点
- 字符串的内容永不可变。【重点】
- 正是因为字符串不可改变,所以字符串是可以共享使用的。
- 字符串效果上相当于是 char[] 字符数组,但是底层原理是 byte[]
创建字符串的四种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用空参构造
String str1 = new String();
// 根据字符数组创建字符串
char[] charArray = { 'A', 'B', 'C' };
String str2 = new String(charArray);
// 根据字节数组创建字符串
byte[] byteArray = { 97, 98, 99 };
String str3 = new String(byteArray);
// 直接创建
String str4 = "hello world";
字符串常量池:程序当中直接写上的双引号字符串,就在字符串常量池中。 内存划分如下:
几种常用方法
比较字符串内容是否相等
1 2
str1.equals(str2) //要比较大小写 strAequalsIgnoreCase(strB) // 直接忽略大小写
- 几点注释
- equals 方法具有对称性,也就是 a.equals(b)和 b.equals(a)效果一样。
- 推荐:”abc”.equals(str) 不推荐:str.equals(“abc”),因为空值 null 会报错。
- 几点注释
字符串的长度
` string.length();`
- 获取指定位置的单个字符
string.charAt(1)
查找参数字符串在本来字符串当中出现的第一次索引位置
1 2
String original = "HelloWorldHelloWorld"; int index = original.indexOf("llo");
截取字符串的方法
1 2 3 4
String str1 public String substring(int index):截取从参数位置一直到字符串末尾,返回新字符串。 public String substring(int begin, int end):截取从begin开始,一直到end结束,中间的字符串。 备注:[begin,end),包含左边,不包含右边。
几种转换
1 2 3 4 5 6 7 8 9 10 11 12 13
//转换成字符数组 char[] chars = "Hello".toCharArray(); // 转换成字节数组 byte[] bytes = "abc".getBytes(); // 字符串内容替换 String str1 = "How do you do?"; String str2 = str1.replace("o", "*"); // 字符串的分割 String str1 = "aaa,bbb,ccc"; String[] array1 = str1.split(","); //以句号为分割,正则表达式
static 关键字 ⭐⭐
定义与使用
类变量
- 当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改 该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。
- 定义格式
static 数据类型 变量名;
静态方法
- 当 static 修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要 创建类的对象。调用方式非常简单。
- 静态方法的使用: 直接类名称.方法名调用
基本格式如下
1 2 3 4
static int numStatic; // 静态变量 public static void methodStatic() { System.out.println(numStatic); }
几点注意:
- 静态不能直接访问非静态。
- 静态方法当中不能用 this。
静态的内存图
- 静态代码块 当第一次用到本类时,静态代码块执行唯一的一次。静态内容总是优先于非静态,所以静态代码块比构造方法先执行。
1 2 3
static { System.out.println("静态代码块执行!"); }
Arrays 类
1
2
3
4
5
6
7
int[] intArray = {10, 20, 30};
//将参数数组变成字符串(按照默认格式:[元素1, 元素2, 元素3...])
String intStr = Arrays.toString(intArray);//[10, 20, 30]
Arrays.sort(intArray); //对数组进行排序
math 类
1
2
3
4
System.out.println(Math.abs(3.14)); //取绝对值
System.out.println(Math.ceil(3.9)); // 向上取整
System.out.println(Math.floor(30.1)); //向下取整
System.out.println(Math.round(20.4)) //四舍五入
继承 super this 抽象类
继承
- 定义 :就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
继承的基本格式
1
2
3
4
5
6
7
public class 父类名称 {
//...
}
public class 子类名称 extends 父类名称{
}
继承后的成员变量
调用规则
- 直接通过子类对象访问成员变量: 等号左边是谁,就优先用谁,没有则向上找。
- 间接通过成员方法访问成员变量: 该方法属于谁,就优先用谁,没有则向上找。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 局部变量: 直接写成员变量名 // 本类的成员变量: this.成员变量名 // 父类的成员变量: super.成员变量名 public class Zi extends Fu { int num = 20; public void method() { int num = 30; System.out.println(num); // 30,局部变量 System.out.println(this.num); // 20,本类的成员变量 System.out.println(super.num); // 10,父类的成员变量 }
继承后的成员方法
两组概念
- 重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写。
- 重载(Overload):方法的名称一样,参数列表【不一样】。
- 几点注意事项
- @Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
1 2 3 4
@Override public String method() { return null; }
- 子类方法的返回值必须【小于等于】父类方法的返回值范围。object 为最高子类
- 子类方法的权限必须【大于等于】父类方法的权限修饰符。public > protected > (default) > private
- @Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
重写的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public class Phone { public void send() { System.out.println("发短信"); } public void show() { System.out.println("显示号码"); } } public class NewPhone extends Phone { @Override public void show() { super.show(); // 直接继承父类 System.out.println("显示姓名"); System.out.println("显示头像"); } }
继承后的特点—构造方法 【重难点】
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 子类的构造方法中默认有一个 super() ,表示调用父类的构造方法
子类必须调用父类构造方法,不写则赠送 super();写了则用写的指定的 super 调用,super 只能有一个,还必须是第一个。 结合代码说明如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class Fu { public Fu() { System.out.println("父类无参构造"); } public Fu(int num) { System.out.println("父类有参构造!"); } } public class Zi extends Fu { public Zi() { super(); // 在调用父类无参构造方法 // super(20); // 在调用父类重载的构造方法 System.out.println("子类构造方法!"); } public void method() { // super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。 } }
super 关键字与 this 关键字
- super :代表父类的存储空间标识(可以理解为父亲的引用)。
- this :代表当前对象的引用(谁调用就代表谁)。
两种使用方法
- 访问成员 略
- 访问构造方法 ⭐⭐ 子类的每个构造方法中均有默认的 super(),调用父类的空参构造。手动调用父类构造会覆盖默认的 super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
1 2 3 4 5
public Zi() { //super(); // 这一行不再赠送 this(123); // 本类的无参构造,调用本类的有参构造 //this(1, 2); // 错误写法! }
继承的特点
- Java 只支持单继承,不支持多继承。
- Java 支持多层继承(继承体系)。
抽象类
- 抽象方法:就是加上 abstract 关键字,然后去掉大括号,直接分号结束。
public abstract void run();
- 抽象类:抽象方法所在的类,必须是抽象类才行。在 class 之前写上 abstract 即可。
public abstract class Animal{}
抽象类的一般使用方法
- 不能直接创建 new 抽象类对象。
- 必须用一个子类来继承抽象父类。
- 子类必须覆盖重写抽象父类当中所有的抽象方法。⭐⭐
- 可以有如下情况
1 2 3 4 5 6 7 8 9 10 11 12
public abstract class A1{ void1(); void2(); } public abstract class A2 extends A1{ @override void1(); } public abstract class A3 extends A2{ @override void2(); }
- 可以有如下情况
- 创建子类对象进行使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// 创建抽象父类 public abstract class fu{ // 在父类创建一个抽象方法 public abstract void eat(); } // 创建一个子类继承抽象父类 public class Zi extends Animal { @overridw public void eat(){ System.out.println("xyx"); } } //主函数 public class DemoMain { public static void main(String[] args) { //Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象 Cat cat = new Cat(); cat.eat(); } }
一个综合案例–群发红包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// 创建主类
public class User {
private String name;
private int money;
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
public void show() {
System.out.println("我叫:" + name + ",我有多少钱:" + money);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
// 创建成员类
public class Member extends User {
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
public void receive(ArrayList<Integer> list) {
int index = new Random().nextInt(list.size());
int delta = list.remove(index);
int money = super.getMoney();
super.setMoney(money + delta);
}
}
//发红包的人
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
super(name, money);
}
public ArrayList<Integer> send(int totalMoney, int count) {
ArrayList<Integer> redList = new ArrayList<>();
int leftMoney = super.getMoney();
if (totalMoney > leftMoney) {
System.out.println("余额不足");
return redList;
}
super.setMoney(leftMoney - totalMoney);
int avg = totalMoney / count;
int mod = totalMoney % count;
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}
int last = avg + mod;
redList.add(last);
return redList;
}
}
// 主函数
public class MainRedPacket {
public static void main(String[] args) {
Manager manager = new Manager("群主", 100);
Member one = new Member("成员A", 0);
Member two = new Member("成员B", 0);
Member three = new Member("成员C", 0);
manager.show();
one.show();
two.show();
three.show();
System.out.println("===============");
ArrayList<Integer> redList = manager.send(20, 3);
one.receive(redList);
two.receive(redList);
three.receive(redList);
manager.show();
one.show();
two.show();
three.show();
}
}
接口与多态
接口
- 定义: 简单来说,接口就是多个类的公共规范。 接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做 是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
接口的使用步骤
- 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
- 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
- 创建实现类的对象,进行使用。
- 接口的定义:
1 2 3
public interface 接口名称 { // 接口内容 }
- 接口中可以包含的内容
- 常量
- 抽象方法
- 默认方法
- 静态方法
- 私有方法
接口的抽象方法
在任何 java 版本中,接口都能定义抽象方法。
1
2
3
4
public inerface MyAbstract {
public abstract void methodAbs1();
public abstract void methodAbs2();
}
几点注意:
- 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
- 这两个关键字修饰符,可以选择性地省略。
- 方法的三要素,可以随意定义。
具体步骤如下:
//定义接口
public interface live {
public abstract void eat();
}
//定义实现类
public class Animal implements live {
@override
public void eat(){
System.out.println("hello");
}
}
// 定义测试类
public class demo {
public stastic void main(String[] args){
Animal a = new Animal();
a.eat();
}
}
接口的默认方法
- 定义:从 java8 开始,接口允许定义默认方法,主要格式为: public default 返回值类型 方法名称(参数列表) { //方法体 }
几点注意:
- 接口的默认方法,可以通过接口实现类对象,直接调用。
- 接口的默认方法,也可以被接口实现类进行覆盖重写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 接口的定义 public interface MyInterfaceDefault { // 抽象方法 public abstract void methodAbs(); // 默认方法 public default void methond_default(); } // 接口的继承与重写 public class MyInterfaceDefaultA implements MyInterfaceDefault { @Override public void methodAbs() { System.out.println("实现了抽象方法,AAA"); } } // 默认方法可以直接继承,也可以覆盖重写。 // 定义测试类 // 略
接口的静态方法
定义: 不能通过接口实现类的对象来调用接口当中的静态方法。通过接口名称,直接调用其中的静态方法,也无法重写静态方法。
1
2
3
4
5
6
7
8
// 接口的定义方式
public interface MyInterfaceStatic {
public static void methodStatic() {
System.out.println("这是接口的静态方法!");
}
}
一个小例子,引出私有方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//接口的定义
public interface MyInterfacePrivateB {
public static void methodStatic1() {
System.out.println("静态方法1");
methodStaticCommon();
}
public static void methodStatic2() {
System.out.println("静态方法2");
methodStaticCommon();
}
//私有方法使用
private static void methodStaticCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}
}
// 接口的调用测试
public class Demo04Interface {
public static void main(String[] args) {
MyInterfacePrivateB.methodStatic1();
MyInterfacePrivateB.methodStatic2();
// 错误写法!
// MyInterfacePrivateB.methodStaticCommon();
}
}
私有方法
- 问题描述:我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。但是这个共有方法不应该让实现类使用,应该是私有化的。
- 两个分类
- 普通私有方法,解决多个默认方法之间重复代码问题 private 返回值类型 方法名称(参数列表) { 方法体 }
- 静态私有方法,解决多个静态方法之间重复代码问题 private static 返回值类型 方法名称(参数列表) { 方法体 }
接口中定义“成员变量”的方法
- 定义
1 2 3 4
public interface MyInterfaceConst { // 这其实就是一个常量,一旦赋值,不可以修改 public static final int NUM_OF_MY_CLASS = 12; }
- 几点注意
- 接口当中的常量,可以省略 public static final,注意:不写也照样是这样。
- 接口当中的常量,必须进行赋值;不能不赋值。
- 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。
接口的多体现
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接 口的多实现。并且,一个类能继承一个父类,同时实现多个接口。 几点注意:
- 接口是没有静态代码块或者构造方法的。
- 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
1 2 3 4
class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... { // 重写接口中抽象方法【必须】 // 重写接口中默认方法【不重名时可选】 }
- 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
- 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
- 如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
- 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。⭐⭐
抽象方法
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
默认方法
接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。
静态方法
接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。
优先级问题
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执 行父类的成员方法。
接口的多继承问题
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继 承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。
多态
多态的前提
- 继承或者实现【二选一】
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
一个例子 格式: 父类名称 对象名 = new 子类名称(); 或者: 接口名称 对象名 = new 实现类名称();
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
// 父类 public class Fu { public void method() { System.out.println("父类方法"); } public void methodFu() { System.out.println("父类特有方法"); } } // 子类 public class Zi extends Fu { @Override public void method() { System.out.println("子类方法"); } } //主测试函数 public class Demo01Multi { public static void main(String[] args) { // 使用多态的写法 // 左侧父类的引用,指向了右侧子类的对象 Fu obj = new Zi(); obj.method(); obj.methodFu(); } }
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写 后方法。
多态的访问原则
- 访问成员变量的两种方式:
- 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
- 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
在多态的代码当中,成员方法的访问规则是:
- 看 new 的是谁,就优先用谁,没有则向上找。
- 口令可总结为: 成员变量:编译看左边,运行还看左边。 成员方法:编译看左边,运行看右边。
多态的好处
引用类型转换
多态的转型分为向上转型和向下转型两种
向上转型 多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
使用格式:
1
父类类型 变量名 = new 子类类型(); 如:Animal a = new Cat();
一个弊端 向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端: 对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
向下转型
- 为什么要向下转型? 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥 有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子 类特有的方法,必须做向下转型。
父类类型向子类类型向下转换的过程,这个过程是强制的。
1 2 3 4 5 6
Animal animal = new Cat(); // 本来创建的时候是一只猫 animal.eat(); // 猫吃鱼 //animal.catchMouse(); // 错误写法!属于子类特有方法 // 向下转型,进行“还原”动作 Cat cat = (Cat) animal; cat.catchMouse(); // 猫抓老鼠
转型异常的问题 引用
instanceof
函数1 2 3 4
if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.catchMouse(); }
final、权限、内部类、引用类型
final
final 常见的地中用法
- 可以用来修饰一个类
- 可以用来修饰一个方法
- 还可以用来修饰一个局部变量
- 还可以用来修饰一个成员变量
用来修饰类
当 final 关键字用来修饰一个类的时候,格式:
1 2 3
public final class 类名称 { // ... }
注意:一个类如果是 final 的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)
用来修饰一个方法 当 final 关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
1 2 3
修饰符 final 返回值类型 方法名称(参数列表) { // 方法体 }
⭐⭐ 对于类、方法来说,abstract 关键字和 final 关键字不能同时使用,因为矛盾。
用来修饰一个局部变量 “一次赋值,终生不变”
final int num = 30
- 对于基本类型来说,不可变说的是变量当中的数据不可改变
- 对于引用类型来说,不可变说的是变量当中的地址值不可改变
1 2 3 4
final Student stu2 = new Student("高圆圆"); System.out.println(stu2.getName()); // 高圆圆 stu2.setName("高圆圆圆圆圆圆"); System.out.println(stu2.getName()); // 高圆圆圆圆圆圆
用来修饰一个成员变量 对于成员变量来说,如果使用 final 关键字修饰,那么这个变量也照样是不可变。
- 由于成员变量具有默认值,所以用了 final 之后必须手动赋值,不会再给默认值了。
- 对于 final 的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
- 必须保证类当中所有重载的构造方法,都最终会对 final 的成员变量进行赋值。
权限修饰符
- java 的四种权限符的访问能力
内部类
- 定义:将一个类 A 定义在另一个类 B 里面,里面的那个类 A 就称为内部类,B 则称为外部类。
- 分类:
- 成员内部类
- 局部内部类(包含匿名内部类)
成员内部类
举例
1 2 3 4 5 6
修饰符 class 外部类名称 { 修饰符 class 内部类名称 { // ... } // ... }
成员内部类的两种使用方式
- 间接方式:在外部类的方法当中,使用内部类;然后 main 只是调用外部类的方法。
- 直接方式:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
重名问题的解释:
1 2 3 4 5 6
public void methodInner() { int num = 30; // 内部类方法的局部变量 System.out.println(num); // 局部变量,就近原则 System.out.println(this.num); // 内部类的成员变量 System.out.println(外部类名称.this.num); // 外部类的成员变量 }
⭐⭐ 出现重名现象,格式为 外部类名称.this.外部类成员变量名
局部内部类
- 定义: 如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。只有当前所属方法才能使用它
基本格式:
1 2 3 4 5 6 7
修饰符 class 外部类名称 { 修饰符 返回值类型 外部类方法名称(参数列表) { class 局部内部类名称 { // ... } } }
类的权限修饰符 public > protected > (default) > private 定义一个类的时候,权限修饰符规则:
- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写
几点注意
- 局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效 final 的】。
- 原因:
- new 出来的对象在堆内存当中。
- 局部变量是跟着方法走的,在栈内存当中。
- 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
- 但是 new 出来的对象会在堆当中持续存在,直到垃圾回收消失。
例子:
1 2 3 4 5 6 7 8 9
public void methodOuter() { int num = 10; // 所在方法的局部变量 class MyInner { public void methodInner() { System.out.println(num); } } }
匿名内部类
定义
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
- 格式 接口名称 对象名 = new 接口名称() { // 覆盖重写所有抽象方法 };
对格式“new 接口名称() {…}”进行解析:
- new 代表创建对象的动作
- 接口名称就是匿名内部类需要实现哪个接口
- {…}这才是匿名内部类的内容
- 几点注意
- 匿名内部类,在【创建对象】的时候,只能使用唯一一次。 如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
- 匿名对象,在【调用方法】的时候,只能调用唯一一次。 如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
- 匿名内部类和匿名对象不是一回事!!! 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
几个成员变量的扩展
class 作为成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class Hero {
private String name; // 英雄的名字
private int age; // 英雄的年龄
private Weapon weapon; // 英雄的武器
}
public Hero(String name, int age, Weapon weapon) {
this.name = name;
this.age = age;
this.weapon = weapon;
}
public void attack() {
System.out.println("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方。");
}
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;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
public class Weapon {
private String code; // 武器的代号
public Weapon() {
}
public Weapon(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
public class DemoMain {
public static void main(String[] args) {
// 创建一个英雄角色
Hero hero = new Hero();
// 为英雄起一个名字,并且设置年龄
hero.setName("盖伦");
hero.setAge(20);
// 创建一个武器对象
Weapon weapon = new Weapon("AK-47");
// 为英雄配备武器
hero.setWeapon(weapon);
// 年龄为20的盖伦用多兰剑攻击敌方。
hero.attack();
}
}
接口作为成员变量
略