目录
一、数组
1、数组创建语法
数据类型[] 变量名 = new 数据类型[长度];
2、用数组创建的对象与用类创建的对象的区别
对象的创建方式 | 对象中的元素个数 | 元素的数据类型 | 元素的访问方式 | 是否支持使用循环来遍历访问元素 | 元素是否有零值 | 对象存储的位置 |
---|---|---|---|---|---|---|
使用类创建 | 由成员变量的个数决定 | 可以在类中自定义 | 对象名.成员变量名 | 否 (无索引号) | 有 | 堆区 |
使用数组创建 | 由数组的长度决定 | 所有元素是同一种类型 | 数组名[索引号] | 是 (有索引号, 从0开始) | 有 | 堆区 |
3、基本数据类型和引用数据类型
- 基本数据类型的数组有8个
- byte[]
- short[]
- int[]
- long[]
- float[]
- double[]
- boolean[]
- char[]
- 引用数据类型的数组(使用类创建的数组)
使用类可以造对象,一个对象可以存储一行数据
使用类可以造数组,一个数组可以存储多个对象,称为对象数组,可以存储一个二维表结构的数据
一个普通Java对象 存储一行数据
一个基本类型数组 存储一列数据
一个引用类型数组 存储一张二维表
package com.tongda.chapter04;
public class Demo01 {
public static void main(String[] args) {
//创建多个对象繁琐
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
Student s4 = new Student();
Student s5 = new Student(); //对象的构造方法是() 可以有参/无参
//用数组就简单很多,一次性创建
//优点:方法栈只需要一个数组变量就可以操作多行数据
Student[] sArray = new Student[10]; //数组的构造方法是[],必须有一个长度参数
System.out.println(sArray);
//遍历数组
for (int i = 0; i < sArray.length; i++) {
//默认值为null
System.out.println(sArray[i]);//与sArray[0]地址不同
// 因为sArray是一个对象地址,sArray[0]是sArray下标为0所在的对象地址
}
//造对象并存入到数组中
// sArray = new Student(); //左:Student[]类型 右:Student类型
// 不能直接把学生对象赋给sArray变量
sArray[0] = new Student();
sArray[1] = new Student();
sArray[2] = new Student();
sArray[3] = new Student();
sArray[4] = new Student();
System.out.println(sArray);
for (int i = 0; i < sArray.length; i++) {
System.out.println(sArray[i]); //使用数组名[索引号]访问数组中的元素
}
}
}
4、数组使用常见错误
错误一:访问未初始化/未开辟内存空间的数组
public class Demo02 {
public static void main(String[] args) {
int[] array;
// 数组未初始化之前不能访问数组中的元素
array[0] = 10;
}
}
错误二: 初始化数组未指定长度
public class Demo02 {
public static void main(String[] args) {
//int[] array = new int[]; // 错误: 初始化数组的时候必须指定数组的长度
// 初始化数组, 并分配长度为10的内存空间
int[] array = new int[10]; // 初始化数组的时候必须指定数组的长度
}
}
错误三: 数组下标越界
public class Demo02 {
public static void main(String[] args) {
// 初始化数组, 并分配长度为10的内存空间
int[] array = new int[10]; // 初始化数组的时候必须指定数组的长度
// array[-1] = 10;
// array[10] = 10;
}
}
5、创建数组的四种方式
public class Demo02 {
public static void main(String[] args) {
// 方式1:1. 在栈区声明数组变量 2. 在堆区开辟数组内存空间 (两行代码)
int[] array;
array = new int[10];
// 方式2:1. 在栈区声明数组变量 2. 在堆区开辟数组内存空间 (一行代码)
int[] array2 = new int[10];
// 方式3:1. 在栈区声明数组变量 2. 在堆区开辟数组内存空间 3. 在数组对象中的每个索引中存入数据
int[] array3 = new int[]{10,20,30,40,50};
// 方式4是方式3的简化版:1. 在栈区声明数组变量 2. 在堆区开辟数组内存空间 3. 在数组对象中的每个索引中存入数据
int[] array4 = {10,20,30,40,50};
}
}
6、遍历数组的三种方式
public class Demo03 {
public static void main(String[] args) {
int[] array = {10,20,30,40,50};
//正序遍历 array.fori
System.out.println("正序遍历");
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]); //此处i所指的是下标
}
//倒序遍历 array.forr
System.out.println("倒序遍历");
for (int i = array.length - 1; i >= 0; i--) {
System.out.println(array[i]); //此处i所指的是下标
}
//不使用索引号遍历,增强型for循环 array.for
System.out.println("不使用索引号遍历");
//优点:语法最easy 缺点:循环中没有索引号
for (int e : array){
System.out.println(e); //此处e所指的是元素
}
}
}
7、二维数组
public static void main(String[] args) {
//二维数组:了解即可,更多使用的是对象数组,对象数组本质上就是特殊的二维数组
String[][] array = new String[5][];
//为二维数组中第一个元素开辟一个一维数组
array[0] = new String[5];
array[1] = new String[5];
array[2] = new String[5];
array[3] = new String[5];
array[4] = new String[5];
//第一个[]表示在二维数组中找到某个一维数组
//第二个[]表示在找到的一维数组中找某个索引号中的元素
array[0][1] = "";
}
二、面向对象的三大特征
1、封装
- 使用private修饰符,修饰类的成员变量,让外部不可以直接对类的成员变量进行读写操作,防止写入非法数据到对象中
- 针对每一个私有属性提供一组public修饰符修饰的setter()写方法和getter()读方法给外部调用
- 在setter()方法和getter方法中可以编写读写之前的数据校验、逻辑判断、格式转换等代码
- setter()方法有参数,无返回值
- getter()方法有返回值,但是没有参数
public class Demo05 {
public static void main(String[] args) {
Person person = new Person();
//存入对象中的数据是一些不合法的数据,怎么避免?
//直接给成员遍历赋值这个过程无法检查赋的值是否合法
//person.name = null;
//person.age = -18;
//使用private修饰了name和age,这两个成员变量就被Person类隐藏了
//name和age不再直接暴露给外部,只在Person类内部可见
//person.name = "张三";//报错
//为什么不直接赋值,而构造一个成员方法?如此麻烦!
//通过参数传入想要存入在name成员变量中的数据
//好处:可以在赋值之前进行检查,过滤非法数据/校验数据
person.setName("张三");
person.setAge(-1);
//私有后对外部不可见,必须提供对私有属性的读写方法给外部调用
//私有的目的,不是让外部可以访问,而是不可以直接访问,防止外部赋予非法数据
//System.out.println(person.name);
//System.out.println(person.age);
//编写get方法来对private数据进行读写
person.getName();
person.getAge();
}
}
public class Person {
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public void setAge(int age){
if(age < 18){
age = 18;
}
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
}
2、继承
I 四种访问权限修饰符
访问权限 | 本类 | 本包的类 | 子类 | 非子类的外包类 | 注释 |
---|---|---|---|---|---|
public | 是 | 是 | 是 | 是 | 本包/非本包/子类访问 |
protected | 是 | 是 | 是 | 否 | 本包/子类访问 |
default | 是 | 是 | 否 | 否 | 本包访问 |
private | 是 | 否 | 否 | 否 | 本类访问 |
II 继承
- 继承发生在两个类之间,是类与类之间的一种关系,被继承的类为父类,继承的类为子类,关键字为extends
- 造子类对象的时候,会同时造出一个父类对象,并且是先造父类对象,再造子类对象,因为类的构造方法中的第一行处,有一句隐式的 super(); 代表调用其父类的无参构造方法
- 造出来的父类对象存储在子类中的super变量中,子类可以通过super这个变量访问父类对象中的成员
- 子类可以继承到父类的所有非private修饰的成员变量和成员方法
- 子类不可以继承父类的构造方法,但可以通过super();来调用父类的构造方法
- 如果一个类没有继承任何类,那么将默认继承Object类,Object类是官方提供了一个所有类的顶层父类
- Java不支持多继承,但支持多级继承,即一个子类只能有一个直接父类,但可以有多个间接父类
public class Father extends Person{
String name = "大王";
protected String name1 = "大王1";
private String name2 = "大王2";
static String name3 = "大王3";
public String name4 = "大王4";
public Father(){
System.out.println("执行Father的构造方法");
}
public void drive(){
System.out.println("开车去诗和远方~");
}
}
/*
* Son extends Father Son继承了Father
* Son - 子类 Father - 父类
* */
public class Son extends Father{
String name = "小王";
public Son(){
//super();隐性suoer()方法
System.out.println("执行Son的构造方法");
}
public void show(){
System.out.println("Son name:" + this.name);
System.out.println("Father name:" + super.name);
System.out.println("Father name1:" + super.name1);//非private都可以访问
// System.out.println("Father name2:" + super.name2);//private不能访问
System.out.println("Father name3:" + super.name3);
System.out.println("Father name4:" + super.name4);
}
public static void main(String[] args) {
//调用Son()构造方法,造了一个Son对象
Son son = new Son();
son.show();
son.drive();
}
}
III 查看类图关系
IV 加入类
官方的类,搜索加入
自己写的类,直接拖过来会自动匹配关系
V 构造方法追溯
son()—>Father()—>Person()—>Object
3、多态
I 源代码文件的四种形式类
① class
普通类
抽象类
父类与子类的关系:一对多
将共有属性抽象出来,让子类去继承父类
继承的好处:
- 相同成员变量和成员方法可以定义在父类中,让所有子类继承,减少冗余代码
- 父类中可以定义抽象方法,让子类重写,这样可以实现运行时多态,减少冗余代码,提高程序的可扩展性
② 接口 interface
③ 枚举 enum
④ 注解 annotation
II 多态
- 编译器多态
通过方法重载实现, 发生在同一个类中, 方法名相同, 但是参数列表不同 - 运行时多态
通过方法重写实现, 必须要有继承关系, 然后子类重写父类的方法, 在编译期使用父类作为数据类型, 即不确定具体的类型, 在运行时, 由传入的具体对象来确定具体的数据
III 重载和重写
重载 Overload 和 重写 Override
相同点: 体现的都是方法的多态, 即一个方法可以实现多种功能
eg:Player 类的 feed 方法可以 实现 喂养各种不同类型的宠物
区别:
重载
- 需要在Player类中定义多个feed方法, 参数是不同的具体类型
- 这是一种编译时的多态
优点: 简单直接, 好理解 (每新增一种宠物, 只需要添加一个feed()方法即可)
缺点: 扩展性差, 代码复用性不足
//feed()方法是编译时多态的,feed()方法发生了方法重载
/**
* 喂食猫
*/
public void feed(Cat cat){ // 在编译期已经确定这个参数必须是Cat类型
}
/**
* 喂食狗
*/
public void feed(Dog dog){ // 在编译期已经确定这个参数必须是Dog类型
}
/**
* 喂食企鹅
*/
public void feed(Penguin pgn){ // 在编译期已经确定这个参数必须是Penguin类型
}
重写
- 必须要有继承
- 只需要在Player类中定义一个feed方法, 然后参数是抽象的父类类型
- 这是一种运行时多态
优点: 扩展性极强, 代码最大程度复用 (每新增一种宠物, 只需要让它继承父类, 然后重写父类的抽象方法即可)
确点: 不易理解
//feed()方法是运行时多态的,eat()方法发生了方法重写
/**
* 喂食宠物
*/
public void feed(Pet pet){
// 在编译期并不确定pet是什么类型, 而是在运行时, feed()方法被调用时, 传入的具体对象来确定pet的类型
}
/*
* Pet - 宠物
* 猫、狗、企鹅...公共的特征(属性/成员变量)和行为(方法)
* 1、如果在各自类中编写他们的属性和方法,必然有大量冗余/重复的代码
* 2、从代码设计角度,可以考虑将这些冗余代码抽象出来,形成一个父类
* */
public abstract class Pet {
String nickName;
int health;
int love;
int level;
/**
* 1、abstract修饰的方法为抽象方法
* 2、为什么不直接把子类中的eat方法直接挪到父类中来?
* 因为多个子类eat方法的逻辑并不是完全一样,无法直接挪到父类让子类继承
* 3、eat方法为什么必须要在父类中定义?
* 若不定义,则pet.eat()报错,因为找不到eat()方法
* 4、eat方法为什么被定义为抽象方法?
* 父类中定义eat()方法是为了pet.eat()语法正确
* 父类的eat方法并不需要写任务代码,因此定义为抽象方法
* @param food
* @return
*/
//---设置eat方法为抽象方法为抽象方法,无需写任何代码
public abstract boolean eat(String food);
}
public class Cat extends Pet{
String color;
/**
* 食物只能是"鱼" + 30、"高级猫粮" + 20、"普通猫粮" + 10
* @param food 食物的名称
* @return 成功或失败
*/
public boolean eat(String food){
if(this.health == 100){
System.out.println(this.nickName + ":健康值已经满了,不需要喂食!");
return false;
}
switch (food){
case "普通猫粮":
this.health += 10;
break;
case "高级猫粮":
this.health += 20;
break;
case "鱼":
this.health += 30;
break;
default:
System.out.println(this.nickName + ":食物种类错误!");
return false;
}
this.health = this.health > 100 ? 100 : this.health;
return true;
}
}
public class Dog extends Pet{
String strain;
public boolean eat(String food){
if(this.health == 100){
System.out.println(this.nickName + ":健康值已经满了,不需要喂食!");
return false;
}
switch (food){
case "普通狗粮":
this.health += 8;
break;
case "高级狗粮":
this.health += 16;
break;
case "骨头":
this.health += 25;
break;
default:
System.out.println(this.nickName + ":食物种类错误!");
return false;
}
this.health = this.health > 100 ? 100 : this.health;
return true;
}
}
package com.tongda.chapter04;
public class Player {
String name;
double money;
/*
* 喂食宠物
* */
public void feed(Pet pet){
pet.eat("");
}
/*
* 喂食宠物
* */
// public void feed(Cat cat){
// boolean b = cat.eat("鱼");
// if(b){
// System.out.println("喂食成功,金币-50");
// this.money -= 50;
// }
// }
// public void feed(Dog dog){
// boolean b = dog.eat("骨头");
// if(b){
// System.out.println("喂食成功,金币-50");
// this.money -= 50;
// }
// }
}
package com.tongda.chapter04;
public class Demo06 {
public static void main(String[] args) {
//宠物猫对象
Cat cat = new Cat();
cat.nickName = "喵喵";
cat.health = 50;
cat.love = 50;
cat.level = 1;
//宠物狗对象
Dog dog = new Dog();
dog.nickName = "汪汪";
dog.strain = "拉布拉多";
dog.health = 50;
dog.love = 50;
dog.level = 1;
//宠物企鹅对象
Penguin penguin = new Penguin();
penguin.nickName = "QQ";
penguin.sex = "雌性";
penguin.health = 50;
penguin.love = 50;
penguin.level = 1;
//玩家对象
Player player = new Player();
player.name = "小明";
player.money = 1000;
//玩家喂养宠物
//Cat cat = new Cat(); 正确
player.feed(cat);
//Cat cat = new Dog(); 错误
//player.feed(dog);
player.feed(dog);
//如果引用是父类类型,那么可以指定不同类型的子类对象
//Pet p1 = new Cat(); //继承Pet
//Pet p2 = new Dog(); //继承Pet
//Pet p3 = new Penguin(); //继承Pet
//Pet p4 = new Pig(); //未继承Pet
//player.feed(penguin);
//Pet.eat();//Pet中并没有这个方法
}
}
三、作业
完成使用重载和重写两种方式实现一个feed方法喂养多种宠物, 并思考区别
1、重写
特点:不易理解,但代码扩展性高,可复用度高 Eg: 每增加一种宠物,只需让其继承父类即可 |
package com.tongda.homework20220225;
//定义抽象类
public abstract class Pet {
String nickName;
int health;
int love;
int level = 0;
//定义抽象方法
public abstract boolean eat(String food);
public void setHealth(int health){
if (health <= 100) this.health = health;
}
public void setLove(int love){
if (health <= 100) this.love = love;
}
}
子类
package com.tongda.homework20220225;
//子类继承父类
public class Cat extends Pet{
String color;
//定义eat()方法
public boolean eat(String food){
if(this.health == 100){
System.out.println(this.nickName + ":当前健康值已经满了,不需要喂食了!");
return false;
}
switch (food){
case "普通猫粮":
this.health += 15;
break;
case "高级猫粮":
this.health += 25;
break;
case "鱼":
this.health += 35;
break;
default:
System.out.println(this.nickName + ":我不喜欢吃这个!");
return false;
}
if(this.health > 100){
this.health = 100;
level++;
}
System.out.println("太好了,喂食成功!");
System.out.println(this.nickName + "当前健康值为:" + this.health);
System.out.println(this.nickName + "当前等级为:" + this.level);
return true;
}
}
子类
package com.tongda.homework20220225;
//子类继承父类
public class Dog extends Pet{
String strain;
//定义eat()方法
public boolean eat(String food){
if(this.health == 100){
System.out.println(this.nickName + ":健康值已经满了,不需要喂食!");
return false;
}
switch (food){
case "普通狗粮":
this.health += 40;
break;
case "高级狗粮":
this.health += 50;
break;
case "骨头":
this.health += 60;
break;
default:
System.out.println(this.nickName + ":我不喜欢吃这个!");
return false;
}
if(this.health > 100){
this.health = 100;
level++;
}
System.out.println("太好了,喂食成功!");
System.out.println(this.nickName + "当前健康值为:" + this.health);
System.out.println(this.nickName + "当前等级为:" + this.level);
return true;
}
}
子类
package com.tongda.homework20220225;
//子类继承父类
public class Penguin extends Pet{
String sex;
//定义eat()方法
public boolean eat(String food){
if(this.health == 100){
System.out.println(this.nickName + ":健康值已经满了,不需要喂食!");
return false;
}
switch (food){
case "乌贼":
this.health += 20;
break;
case "磷虾":
this.health += 30;
break;
case "沙丁鱼":
this.health += 40;
break;
default:
System.out.println(this.nickName + ":我不喜欢吃这个!");
return false;
}
if(this.health > 100){
this.health = 100;
level++;
}
System.out.println("太好了,喂食成功!");
System.out.println(this.nickName + "当前健康值为:" + this.health);
System.out.println(this.nickName + "当前等级为:" + this.level);
return true;
}
}
玩家类
package com.tongda.homework20220225;
public class Player {
String name;
double money;
//定义feed方法,参数为父类抽象类型
public void feed(Pet pet, String food){
System.out.println("---------------------------");
if(money <= 50) System.out.println("对不起,您的余额不足50元,请充值!");
else {
if(pet.eat(food)){
this.money -= 50;
System.out.println("您当前余额为:" + this.money);
}//根据参数调用不同对象的eat()方法
}
}
}
游戏类
package com.tongda.homework20220225;
import java.util.Scanner;
public class Game {
public static void main(String[] args) {
//养只猫
Cat cat = new Cat();
cat.nickName = "helloKitty";
cat.setHealth(50);
cat.setLove(50);
cat.level = 1;
cat.color = "白色";
//养只狗
Dog dog = new Dog();
dog.nickName = "小七";
dog.setHealth(50);
dog.setLove(50);
dog.level = 1;
dog.strain = "神犬";
//养只企鹅
Penguin penguin = new Penguin();
penguin.nickName = "QQ";
penguin.setHealth(50);
penguin.setLove(50);
penguin.level = 1;
penguin.sex = "雌";
//一个玩家
Player player = new Player();
player.name = "ABU";
player.money = 1000;
//喂宠物
player.feed(cat,"鱼");
player.feed(dog,"骨头");
player.feed(penguin,"巧克力");
}
}
2、重载
特点:易理解,但代码扩展性及复用度低,可能会造成代码冗余 Eg: 每增加一种宠物,添加对应参数类型的feed方法即可 |
父类
package com.tongda.homework20220225;
public class Pet {
String nickName;
int health;
int love;
int level = 0;
public void setHealth(int health){
if (health <= 100) this.health = health;
}
public void setLove(int love){
if (health <= 100) this.love = love;
}
}
玩家类
package com.tongda.homework20220225;
public class Player {
String name;
double money;
public void feed(Cat cat, String food){
System.out.println("---------------------------");
if(money <= 50) System.out.println("对不起,您的余额不足50元,请充值!");
else {
if(cat.eat(food)){
this.money -= 50;
System.out.println("您当前余额为:" + this.money);
} }
}
public void feed(Dog dog, String food){
System.out.println("---------------------------");
if(money <= 50) System.out.println("对不起,您的余额不足50元,请充值!");
else {
if(dog.eat(food)){
this.money -= 50;
System.out.println("您当前余额为:" + this.money);
}
}
}
public void feed(Penguin penguin, String food){
System.out.println("---------------------------");
if(money <= 50) System.out.println("对不起,您的余额不足50元,请充值!");
else {
if(penguin.eat(food)){
this.money -= 50;
System.out.println("您当前余额为:" + this.money);
}
}
}
}
其他不变