小黑子—Java从入门到入土过程:第六章
Java零基础入门6.0
- Java系列第六章
- 1. 面向对象综合练习
- 1.1 文字版格斗游戏=
- 参数占位,格式化输出回顾
- 关于printf和print和println的区别
- 1.2 对象数组练习
- 1.2.1 练习一
- 1.2.2 练习二
- 1.2.3 练习三
- 1.2.4 练习四
- 1.3 键盘录入回顾
- 1.4 复杂对象数组练习
- 1.4.1 复杂练习一
- 1.4.2 复杂练习二
- 2. API:应用程序编程接口
- 2.1 使用帮助文档
- 3. 字符串String
- 3.1 创建String对象的两种方式
- 3.2 字符串的比较
- 3.3 字符串练习
- 3.3.1 练习一:用户登录
- 3.3.2 练习二 ctrl+alt+v 自动生成左边赋值
- 3.3.3 练习三:字符串拼接和反转
- 3.3.4 练习四
- 3.3.5 练习五
- 3.4 StringBuilder
- 3.4.1 StringBuilder构造方法
- 3.4.2 StringBuilder常用方法
- 3.4.3 链式编程
- 3.4.4 StringJoiner
- 3.4.5 字符串原理
- 扩展底层原理1和2:字符串原理
- 扩展底层原理3:字符串拼接的底层原理
- 扩展底层原理5:StringBuilder提高效率原理
- 扩展底层原理5:StringBuilder源码分析
- ctrl + n IDEA快捷键 出现搜索界面
- 3.4.6 字符串综合练习
- 4. 集合
- 4.1 ArrayList 集合(之一)
- 4.2 集合综合练习
- 4.2.1 添加字符串和整数遍历
- 4.2.2 基本数据类型的包装类
- 4.2.3 添加学生对象并遍历
- 4.2.4 添加用户对象并判断是否存在
- 4.2.5 添加手机对象并返回要求的数据
- 5. 学生管理系统
- 5.1 学生管理系统升级版
- 6. 面向对象进阶
- 6.1 static
- 6.1.1 static-静态变量
- 6.1.2 static-静态方法和工具
- 静态方法
- 工具类
- 6.1.3 static-注意事项
- 6.1.4 重新认识main方法
- 6.2 继承
- 6.2.1 封装
- 6.2.2 继承的描述
- 6.2.3 继承的特点
- 6.2.4 子类继承父类的特性
- 6.2.5 终端的jps和jhsdb hsdb
- 6.2.6 继承中成员变量和成员方法的访问特点
- 继承成员变量
- 继承成员方法
- 6.2.6 继承中的构造方法
- 6.2.7 this、super关键字总结
- 6.3 认识多态
- 6.3.1 多态中调用成员的特点
- 6.3.2 多态的优势和弊端
- 6.3.3 instanceof 判断是不是相应类型
- 6.3.4 多态综合练习
- 6.4 包和 final
- 6.4.1 包
- 6.4.2 final (与static类似的关键字)
- 6.5 权限修饰符和代码块
- 6.5.1 权限修饰符
- 6.5.2 代码块
- 6.6 抽象类和抽象方法
- 6.7 接口
- 6.7.1 接口的细节:成员特点和各种接口的关系
- 6.7.2 接口和抽象类案例
- 6.7.3 接口的新增方法
- jdk8以后接口中新增的默认方法
- jdk8以后接口中新增的静态方法
- jdk9新增的私有方法
- 6.7.4 接口应用
- 6.7.5 适配器设计模型
- 6.8 初始内部类
- 6.8.1 成员内部类
- 6.8.2 静态内部类
- 6.8.3 局部内部类
- 6.8.4 匿名内部类
Java系列第六章
1. 面向对象综合练习
1.1 文字版格斗游戏=
1.简单版
User类包
package test3;
import java.util.Random;
public class User {
private String name;
private int blood;
private char gender;
private String face;//长相随机
String[] boyfaces = {"风流俊雅", "气宇轩昂", "相貌英俊", "五官端正", "相貌平平", "一塌糊涂", "面目狰狞"};
String[] girlfaces = {"美奂绝伦", "沉鱼落雁", "婷婷玉立", "身材娇好", "相貌平平", "相貌简陋", "惨不忍睹"};
//attack攻击描述:
String[] attacks_desc = {
"%s使出了一招【背心钉】,转到对方的身后,一掌向%s背心的灵台穴拍去。",
"%s使出了一招【游空探爪】,飞起身形自半空中变掌为抓锁向%s。",
"%s大喝一声,身形下伏,一招【劈雷坠地】,捶向%s双腿。",
"%s运气于掌,一瞬间掌心变得血红,一式【掌心雷】,推向%s。",
"%s阴手翻起阳手跟进,一招【没逛拦】,结结实实的挺向%s。",
"%s上步抢身,招中套招,一招【劈挂连环】,连环攻向%s"
};
//injured受伤描述:
String[] injureds_desc = {
"结果%s退了半步,毫发无损",
"结果给%s造成一处瘀伤",
"结果一击命中,%s痛得弯下腰",
"结果%s痛苦地闷哼了一声,显然受了点内伤",
"结果%s摇摇晃晃,一跤摔倒在地",
"结果%s脸色一下变得惨白,连退了好几步",
"结果『轰』的一声,%s口中鲜血狂喷而出",
"结果%s一声惨叫,像滩软泥般塌了下去",
};
public User() {
}
public User(String name, int blood, char gengder) {
this.name = name;
this.blood = blood;
this.gender = gender;
//随机长相
setFace(gender);
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getFace() {
return face;
}
public void setFace(char gender) {
Random r = new Random();
//长相随机
if (gender == '男') {
//从boyfaces里面随机长相
int index = r.nextInt(boyfaces.length);
this.face = boyfaces[index];
} else if (gender == '女') {
//从girlfaces里面随机长相
int index = r.nextInt(girlfaces.length);
this.face = girlfaces[index];
} else {
this.face = "面目狰狞";
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBlood() {
return blood;
}
public void setBlood(int blood) {
this.blood = blood;
}
//定义一个方法用于攻击别人
//思考:谁攻击谁?
//r1.攻击(r2)
//方法的调用者去攻击参数
public void attack(User user) {
Random r = new Random();
int index = r.nextInt(attacks_desc.length);
String KungFu = attacks_desc[index];
//输出一个攻击的效果
System.out.printf(KungFu,this.getName(),user.getName());
System.out.println();
//计算造成的伤害
int hurt = r.nextInt(20) + 1;
//修改一下挨揍的人血量
//剩余血量
int remainBoold = user.getBlood() - hurt;
//对剩余血量做一个验证,如果为负数了,就修改为0
remainBoold = remainBoold < 0 ? 0 : remainBoold;
//修改一下挨揍的人血量
user.setBlood(remainBoold);
//受伤的描述
//血量>90 0 索引的描述
//80 ~ 90 1索引的描述
//70 ~ 80 2索引的描述
// 60 ~ 70 3索引的描述
// 40 ~ 60 4索引的描述
// 20 ~ 40 5索引的描述
// 10 ~ 20 6索引的描述
// 小于10的 7索引的描述
if(remainBoold > 90){
System.out.printf(injureds_desc[0],user.getName());
}else if(remainBoold>80&&remainBoold<=90){
System.out.printf(injureds_desc[1],user.getName());
}else if(remainBoold>70&&remainBoold<=80){
System.out.printf(injureds_desc[2],user.getName());
}else if(remainBoold>60&&remainBoold<=70){
System.out.printf(injureds_desc[3],user.getName());
}else if(remainBoold>40&&remainBoold<=60){
System.out.printf(injureds_desc[4],user.getName());
}else if(remainBoold>20&&remainBoold<=40){
System.out.printf(injureds_desc[5],user.getName());
}else if(remainBoold>10&&remainBoold<=200){
System.out.printf(injureds_desc[6],user.getName());
}else{
System.out.printf(injureds_desc[7],user.getName());
}
System.out.println();
}
public void showRoleInfo() {
System.out.println("姓名为" + getName());
System.out.println("血量为" + getBlood());
System.out.println("性别为" + getGender());
System.out.println("长相为" + getFace());
}
}
GameTest类包
package test3;
public class GameTest {
public static void main(String[] args) {
// 1. 创建第一个角色
User u1 = new User("麻瓜",100,'男');
//2.创建第二个角色
User u2 = new User("小老板",100,'男');
//展示角色信息
u1.showRoleInfo();
System.out.println("------------------------");
u2.showRoleInfo();
System.out.println("------------------------");
//3.开始格斗 回合制游戏
while(true){
//u1 开始攻击u2
u1.attack(u1);
//判断u2的剩余血量
if(u2.getBlood()==0){
System.out.println(u1.getName()+"K.O了"+u2.getName());
break;
}
//u2开始攻击u1
u2.attack(u1);
if(u1.getBlood()==0){
System.out.println(u2.getName()+"K.O了"+u1.getName());
break;
}
}
}
}
参数占位,格式化输出回顾
public static void main(String[] args) {
//两部分参数:
//第一部分参数:要输出的内容%s(占位)
// 第二部分参数:填充的数据
System.out.printf("%s你好啊%s","张三","李四");
//但是printf没有换行效果
System.out.println();
System.out.printf("你好啊%s","李四");
}
关于printf和print和println的区别
- print为一般输出,同样不能保留精度格式转化,也不能换行输出
- printf常用于格式转换,但需要注意不是换行输出,只用于精度转换
- println为换行输出,不能用于格式转换
1.2 对象数组练习
1.2.1 练习一
Goods 类包
package test4;
public class Goods {
private String id;
private String name;
private double price;
private int count;
public Goods(){}
public Goods(String id,String name,double price, int count){
this.id = id;
this.name = name;
this.price = price;
this.count = count;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
GoodsTest 引入执行
package test4;
public class GoodsTest {
public static void main(String[] args) {
//1.创建一个数组
Goods[] arr = new Goods[3];
//2.创建三个商品
//快捷键 ctrl+p :把方法所对应的参数所展示
Goods g1 = new Goods("001","华为p40",599.0,100);
Goods g2 = new Goods("002","保温杯",297,50);
Goods g3 = new Goods("003","洗衣机",10000,500);
//3.把商品添加到数组中
arr[0]=g1;
arr[1]=g2;
arr[2]=g3;
//4.遍历
for (int i = 0; i < arr.length; i++) {
// i 索引 arr[i] 元素
Goods goods = arr[i];
System.out.println(goods.getId()+","+goods.getName()+","+goods.getPrice()+","+goods.getCount());
}
}
}
1.2.2 练习二
Car的javabeen包
package test5;
public class Car {
private String brand;//品牌
private int price;//价格
private String color;//颜色
public Car(){}
public Car(String brand, int price, String color) {
this.brand = brand;
this.price = price;
this.color = color;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
CarTest
package test5;
import java.util.Scanner;
public class CarTest {
public static void main(String[] args) {
//1.创建一个数组用来存3个汽车对象
Car[] arr = new Car[3];
//2.创建汽车对象,数据来自于键盘录入
//如果把Car c = new Car();放在循环的外面,那么对象就只有一辆车
//此时下面循环就对同一辆车的属性进行修改,而不是创建新的属性
//所以此创建对象的代码不能写在循环的外面,必须写在循环里面
Scanner sc = new Scanner(System.in);
for (int i = 0; i < arr.length; i++) {
//创建汽车对象
Car c = new Car();
//录入品牌
System.out.println("请输入汽车的品牌:");
String brand = sc.next();
c.setBrand(brand);
//录入价格
System.out.println("请输入汽车的价格:");
int price = sc.nextInt();
c.setPrice(price);
//录入颜色
System.out.println("请输入汽车的颜色:");
String color = sc.next();
c.setColor(color);
//把汽车对象添加到数组当中
arr[i]=c;
}
//3.遍历数组
for (int i = 0; i < arr.length; i++) {
Car car = arr[i];
System.out.println(car.getBrand()+","+car.getPrice()+","+car.getColor());
}
}
}
1.2.3 练习三
Phone的javabeen包
package test6;
public class Phone {
private String brand;//品牌
private int price;//价格
private String color;//颜色
public Phone(){}
public Phone(String brand,int price,String color){
this.brand = brand;
this.price = price;
this.color = color;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
PhoneTest
package test6;
public class PhoneTest {
public static void main(String[] args) {
//1.创建一个数组
Phone[] arr = new Phone[3];
//2.创建手机的对象
Phone p1 = new Phone("xiaomi",1999,"white");
Phone p2 = new Phone("huawei",4999,"blue");
Phone p3 = new Phone("meizhu",2999,"red");
//3.把手机对象添加到数组当中
arr[0]=p1;
arr[1]=p2;
arr[2]=p3;
//4.获取三部手机的平均价格
int sum = 0;
for (int i = 0; i < arr.length; i++) {
//i索引 arr[i]元素(手机对象)
Phone phone = arr[i];
sum = sum + phone.getPrice();
}
//5.求平均值
double avg = sum*1.0/arr.length;
System.out.println(avg);
}
}
1.2.4 练习四
GilFriend的javabeen包
package test7;
public class GirlFriend {
private String name;//姓名
private int age; //年龄
private String gender;//性别
private String hobby;//爱好
public GirlFriend() {}
public GirlFriend(String name, int age, String gender, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = hobby;
}
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
}
GirlFriendTest
package test7;
public class GirlFriendTest {
public static void main(String[] args) {
//1.定义数组存入女朋友的对象
GirlFriend[] arr = new GirlFriend[4];
//2.创建女朋友的对象
GirlFriend gf1 = new GirlFriend("小老板",22,"man","do the game");
GirlFriend gf2 = new GirlFriend("麻瓜",24,"man","van the game");
GirlFriend gf3 = new GirlFriend("马捞C",30,"man","乖乖站好");
GirlFriend gf4 = new GirlFriend("阿克曼",18,"man","fk you");
//3.把对象添加到数组当中
arr[0]=gf1;
arr[1]=gf2;
arr[2]=gf3;
arr[3]=gf4;
//4.求和
int sum =0;
for (int i = 0; i < arr.length; i++) {
GirlFriend gf = arr[i];
//累加
sum += gf.getAge();
}
//5.平均值
int avg = sum/arr.length;
//6.统计年龄比平均值低的有几个,打印他们的信息
int count = 0;
for (int i = 0; i < arr.length; i++) {
GirlFriend gf = arr[i];
if(gf.getAge()<avg){
count++;
System.out.println(gf.getName()+","+gf.getAge()+","+gf.getGender()+","+gf.getHobby());
}
}
System.out.println("一共"+count+"个");
}
}
1.3 键盘录入回顾
键盘录入:
第一套体系:
- nextInt();接收整数
- nextDouble();接收小数
- next();接收字符串
- 遇到空格,制表符,回车就停止接受。这些符号后面的数据就不会接受了
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");//123 123
int num1 = sc.nextInt();//123
System.out.println(num1);
System.out.println("请输入第二个整数:");
int num2 = sc.nextInt();//123
System.out.println(num2);
}
当next中出现空格时,可会给下一个键盘的数据接受,就不会做键盘录入
2.
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str1 = sc.next();
System.out.println(str1);
System.out.println("请输入第二个字符串:");
String str2 = sc.next();
System.out.println(str2);
}
第二套体系:
- nextLine();接收字符串
遇到回车才停止录入
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line1 = sc.nextLine();
System.out.println(line1);
System.out.println("请输入第二个字符串:");
String line2 = sc.nextLine();
System.out.println(line2);
}
但是重要的是,键盘录入的两套体系不能混用
弊端:
- 先用nextInt,再用nextLine会导致下面的nextLine接受不到数据
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
int num = sc.nextInt();//123+回车
System.out.println(num);//123
System.out.println("请输入第一个字符串:");
String line = sc.nextLine();
System.out.println(line);//录入不了,就结束了
}
1.4 复杂对象数组练习
1.4.1 复杂练习一
思考要求1和2思路:
1.创建一个长度为3的数组
2.创建学生对象
3.把学生对象添加到数组当中
4.再次创建一个学生对象
5.唯一性判断
5.1已存在---提示重复
5.2不存在---添加学生对象
6.添加学生对象
6.1老数组已经存满
6.2老数组没有存满
Student的javabeen包:
package test8;
public class Student {
private int id;
private String name;
private int age;
public Student() {}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
}
Test实现要求1和2:
package test8;
public class Test {
public static void main(String[] args) {
//1.创建一个数组用来储存学生对象
Student[] arr = new Student[3];
//2.创建学生对象并添加到数组当中
Student stu1 = new Student(1,"zhangsan",23);
Student stu2 = new Student(2,"lisi",24);
//3.把学生对象添加到数组当中
arr[0]=stu1;
arr[1]=stu2;
//要求1:再次添加一个学生对象,并在添加的时候进行学号的唯一性判断。
Student stu4 = new Student(4,"zhaoliu",26);
//唯一性判断
//已存在--不用添加
//不存在--就可以把学生对象添加进数组
boolean flag = contains(arr,stu4.getId());
if(flag){
//已存在--不用添加
System.out.println("当前id重复,请修改id后再进行添加");
}else{
//不存在--就可以把学生对象添加进数组
//把stu4添加到数组当中
//1.数组已经存满---只能创建一个新的数组,新数组的长度=老数组+1
//2.数组没有存满--直接添加
int count = getCount(arr);
if(count==arr.length){
//已经存满
//创建一个新的数组,长度=老数组的长度+1
//然后把老数组的元素,拷贝到新数组当中
Student[] newArr = creatNewArr(arr);
//把stu4添加进去
newArr[count]=stu4;
//要求2:添加完毕之后,遍历所有学生信息
printArr(arr);
}else{
//没有存满
//[stu1,stu2,null]
//getCount获取到的是2,表示数组当中已经有了2个元素
//还有一层意思:如果下一次要添加数据,就是添加到2索引的位置
arr[count] = stu4;
//要求2:添加完毕之后,遍历所有学生信息,这样写就与上方重复了
//此时就又需要定义方法进行调用
printArr(arr);
}
}
}
public static void printArr(Student[] arr){
for (int i = 0; i < arr.length; i++) {
Student stu = arr[i];
if(stu!=null){
System.out.println(stu.getId()+","+stu.getName()+","+stu.getAge());
}
}
}
//创建一个新的数组,长度=老数组的长度+1
// 然后把老数组的元素,拷贝到新数组当中
public static Student[] creatNewArr(Student[] arr) {
Student[] newArr = new Student[arr.length+1];
//循环遍历得到老数组中的每一个元素
for (int i = 0; i < arr.length; i++) {
//把老数组中的元素添加到新数组当中
newArr[i]=arr[i];
}
//把新数组返回
return newArr;
}
//定义一个方法判断数组中已经存了几个元素
public static int getCount(Student[] arr){
//定义一个计数器用来统计
int count = 0;
for (int i = 0; i < arr.length; i++) {
if(arr[i]!=null){
count++;
}
}
//当循环结束之后,我就知道了数组中一共有几个元素
return count;
}
//1.我要干嘛? 唯一性判断
//2.我干这件事,需要什么才能完成?数组id
//3.调用处是否需要继续使用方法的结果?
public static boolean contains(Student[] arr,int id){
//1 2 3 ----3是否存在
for (int i = 0; i < arr.length; i++) {
//依次获取到数组里面的每一个学生对象
Student stu = arr[i];
//如果没装满,stu获取的是null,用null调用其他的方法就会报错
if(stu!=null){
//获取数组学生的id
int sid = stu.getId();
//比较
if(sid == id){
return true;
}
}
}
//当循环结束之后,还没有找到一样的,那么就表示数组中要查找的id是不存在的。
return false;
}
}
1.4.2 复杂练习二
Test2实现需求3和4
package test8;
public class Test2 {
public static void main(String[] args) {
//1.创建一个数组用来储存学生对象
Student[] arr = new Student[3];
//2.创建学生对象并添加到数组当中
Student stu1 = new Student(1,"zhangsan",23);
Student stu2 = new Student(2,"lisi",24);
Student stu3 = new Student(3,"wangwu",25);
//3.把学生对象添加到数组当中
arr[0]=stu1;
arr[1]=stu2;
arr[2]=stu3;
//要求3:通过id删除学生信息
//如果存在,则删除,如果不存在,则提示删除失败。
//要找到id在数组中对应的索引
int index = getIndex(arr,2);
if(index>=0){
//如果存在,则删除
arr[index]=null;
//遍历数组
printArr(arr);
}else{
//如果不存在,则提示删除失败
System.out.println("当前id不存在,删除失败");
}
}
//1.我要干嘛? 找到id在数组中的索引
//2.我干这件事,需要什么才能完成?数组id
//3.调用处是否需要继续使用方法的结果? 要
public static int getIndex(Student[] arr,int id){
for (int i = 0; i < arr.length; i++) {
//依次得到每一个学生对象
Student stu = arr[i];
//对stu进行一个非空判断
if(stu !=null){
int sid = stu.getId();
if(sid==id){
return i;
}
}
}
//当循环结束之后,还没有找到就表示不存在
return -1;
}
public static void printArr(Student[] arr){
for (int i = 0; i < arr.length; i++) {
Student stu = arr[i];
if(stu!=null){
System.out.println(stu.getId()+","+stu.getName()+","+stu.getAge());
}
}
}
}
Test3实现要求5:
package test8;
public class Test3 {
public static void main(String[] args) {
//1.创建一个数组用来储存学生对象
Student[] arr = new Student[3];
//2.创建学生对象并添加到数组当中
Student stu1 = new Student(1,"zhangsan",23);
Student stu2 = new Student(2,"lisi",24);
Student stu3 = new Student(3,"wangwu",25);
//3.把学生对象添加到数组当中
arr[0]=stu1;
arr[1]=stu2;
arr[2]=stu3;
//4.先要找到id为2的学生对于的索引
int index = getIndex(arr,2);
//5.判断索引
if(index>=0){
//存在,则将他的年龄+1岁
Student stu = arr[index];
//把原来的年龄拿出来
int newAge = stu.getAge()+1;
//把+1之后的年龄塞回去
stu.setAge(newAge);
//遍历数组
printArr(arr);
}else{
//不存在,则直接提示
System.out.println("当前id不存在,修改失败");
}
}
//1.我要干嘛? 找到id在数组中的索引
//2.我干这件事,需要什么才能完成?数组id
//3.调用处是否需要继续使用方法的结果? 要
public static int getIndex(Student[] arr,int id){
for (int i = 0; i < arr.length; i++) {
//依次得到每一个学生对象
Student stu = arr[i];
//对stu进行一个非空判断
if(stu !=null){
int sid = stu.getId();
if(sid==id){
return i;
}
}
}
//当循环结束之后,还没有找到就表示不存在
return -1;
}
public static void printArr(Student[] arr){
for (int i = 0; i < arr.length; i++) {
Student stu = arr[i];
if(stu!=null){
System.out.println(stu.getId()+","+stu.getName()+","+stu.getAge());
}
}
}
}
2. API:应用程序编程接口
例子:
其他的API都是存储在JDK-API帮助文档.CHM
2.1 使用帮助文档
API使用步骤
java.lang
包(非常重要)使用java.lang
包时,不需要import进行导入
API帮助文档网站
可以寻找关键字
API文档练习
public static void main(String[] args) {
//1.创建对象
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个小数:");
//2.接受一个小数
double result = sc.nextDouble();
//3.输出打印
System.out.println(result);
}
3. 字符串String
常见情况:
字符串概述:
注意:
总结:
3.1 创建String对象的两种方式
其有两种:
直接赋值和用new去构造一个方法
public static void main(String[] args) {
//直接赋值
String a="1111";
//空参构造
String b=new String();
System.out.println("1"+b+"2");
//传递字符串(了解)
String c=new String("abc");
//传递字符数组
//应用环境:修改字符串 例如把abc 改成qbc
//abc-->{'a','b','c'}-->{'q','b','c'}-->""qbc
char[] d={'w','s','s','b'};
String e=new String(d);
System.out.println(e);
//传递字节数组
//应用场景:在网络中传输的都是字节信息,把字节信息转换成字符串
byte[] f={97,98,99,100};
//这里不是把数字传递过去,而是把数字转成ASCLL表里的字符。
String g=new String(f);
System.out.println(g);
}
字符串在内存中的存储方式
字符串常量池
只有直接赋值的字符串才会在这个空间中 在堆内存中
当串池中已经存在一个abc字符串时,再次给字符串变量赋值相同的值时,会直接复用之前相同字符串的地址。
每new一次,就会在堆空间创建一个新的空间,可能会造成内存的浪费
所以,还是推荐使用直接赋值创建字符串。
3.2 字符串的比较
==号比较的原理
字符串常见比较错误:
对于不同方式字符串的内容比较:
例如:
1.
public static void main(String[] args) {
//1.创建两个字符串对象
String s1 = new String("abc");
String s2 = "Abc";
//2.==号比较
//基本数据类型:比的是数据值
//应用数据类型:比的是地址值
System.out.println(s1==s2);//false
//3.比较字符串对象中的内容是否相等
boolean result = s1.equals(s2);
System.out.println(result);
//4.比较字符串对象中的内容是否相等,忽略大小写
boolean result2 = s1.equalsIgnoreCase(s2);
System.out.println(result2);// true
}
public static void main(String[] args) {
//1.假设我现在键盘录入一个abc
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str1 = sc.next();//abc 是new出来的
//2.代码中再定义一个字符串abc
String str2 = "abc";
//3.用==比较,这两者能一样吗?
System.out.println(str1 == str2);//false,为什么?直接赋值的和new出来的不一样
//结论:
//以后只要想比较字符串的内容,就必须要用string里面的方法
}
3.3 字符串练习
3.3.1 练习一:用户登录
public static void main(String[] args) {
// 1.定义两个变量记录正确的用户名和密码
String rightUsername = "zhangsan";
String rightPassword = "123456";
Scanner sc =new Scanner(System.in);
//2.键盘录入用户名和密码
for (int i = 0; i < 3; i++) {
System.out.println("请输入用户名:");
String username = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
//3.比较
if(username.equals(rightUsername)&&password.equals((rightPassword))){
System.out.println("用户登录成功");
break;
}else{
if(i==2){
// 最后一次机会也输入错误,此时要提示账号被锁定
System.out.println("账号"+username+"被锁定");
}else{
System.out.println("用户登录失败,用户名或密码有误,您还剩下"+(2-i)+"次机会");//
}
}
}
}
3.3.2 练习二 ctrl+alt+v 自动生成左边赋值
chcharAt()方法:返回每一个索引对应的索引值
public static void main(String[] args) {
// 1. 键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
//2.进行遍历
for (int i = 0; i < str.length(); i++) {
//i 依次表示字符串的每一个索引
//chcharAt()方法:返回每一个索引对应的索引值
// ctrl+alt+v 自动str.charAt(i);生成左边
char c = str.charAt(i);
System.out.println(c);
}
2.
public static void main(String[] args) {
// 1. 键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
//2.统计---计数器思想
//定义三个计数器
int bigCount = 0;
int smallCount = 0;
int numberCount = 0;
for (int i = 0; i < str.length(); i++) {
//i 依次表示字符串中的每一个索引
char c = str.charAt(i);
if(c>='a'&&c<='z'){
//char类型的变量在参与计算的时候自动类型提升为int查询ascii码表
smallCount++;
}else if(c>='A'&&c<='Z'){
bigCount++;
}else if(c>='0'&&c<='9'){
numberCount++;
}
}
System.out.println("小写字母有:"+smallCount+"个");
System.out.println("大写字母有:"+bigCount+"个");
System.out.println("数字字母有:"+numberCount+"个");
}
3.3.3 练习三:字符串拼接和反转
public static void main(String[] args) {
//1.我要干嘛?---遍历数组并把数组拼接成一个字符串
//2.我干这件事情需要什么才能完成?---数组
//3.我干完了是否要把结果返回给调用处---返回一个拼接之后的字符串
// 如果调用处需要继续使用,那么必须返回
//如果调用处不需要继续使用,那么可以返回也可以不返回
int[] arr={1,2,3};
String str = arrToString(arr);
System.out.println(str);//123
}
public static String arrToString(int[] arr){
if(arr==null){
return"";
}
if(arr.length==0){
return "[]";
}
String result = "[";
//当代码执行到这里表示什么?
//表示数组不是null,也不是长度为0的
for (int i = 0; i < arr.length; i++) {
if(i==arr.length-1){
result = result+arr[i];
}else{
result = result+arr[i]+",";
}
}
//此时拼接右括号
result = result + "]";
return result;
}
2.
public class ArithmeticoperatorDemo1 {
public static void main(String[] args) {
String result = reverser("cnm");
System.out.println(result);
}
public static String reverser(String str){
String result = "";
for (int i = str.length()-1; i >=0; i--) {
char c = str.charAt(i);
result += c;
}
return result;
}
}
3.3.4 练习四
3.3.5 练习五
3.4 StringBuilder
使用StringBuilder的场景:
- 1.字符串的拼接
- 2.字符串的反转
相当于一个字符串的工具,StringBuilder不是字符串类型!!!!
3.4.1 StringBuilder构造方法
3.4.2 StringBuilder常用方法
1.append 添加方法
public static void main(String[] args) {
//1.创建对象
StringBuilder sb = new StringBuilder();
//打印
//普及:
//因为stringBuilder是Java已经写好的类
// java在底层对他做了一些特殊处理。
//打印对象不是地址值而是属性值。
System.out.println(sb);
}
所以它没有返回一个地址值
public static void main(String[] args) {
//1.创建对象
StringBuilder sb = new StringBuilder("abc");
//2.添加元素
sb.append(1);
sb.append(2.3);
sb.append(true);
System.out.println(sb);
}
2.reverse 反转方法
sb.reverse();
3.length 获取长度方法
int len = sb.length();
System.out.println(len);
4.toString
public static void main(String[] args) {
//1.创建对象
StringBuilder sb = new StringBuilder("abc");
//2.添加字符串
sb.append("aaa");
sb.append("wwww");
sb.append("xxxx");
System.out.println(sb);//abcaaawwwwxxxx只是在一个容器里面
//3.再把StringBuilder变回字符串类型
String str = sb.toString();
System.out.println(str);//现在的abcaaawwwwxxxx才是字符串类型
}
3.4.3 链式编程
当我们在调用一个方法的时候,不需要用变量接收他的结果,可以继续调用其他方法
1.
public static void main(String[] args) {
String len = getString().substring(1).replace('b','w');
System.out.println(len);
int len2 = len.length();
System.out.println(len2);
}
public static String getString(){
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
return str;
}
2.
将上方3.4.2内容的第4点:
sb.append("aaa");
sb.append("wwww");
sb.append("xxxx");
可写成->一条链子,效果相同
sb.append("aaa").append("wwww").append("xxxx");
练习:
1.
equals() 方法用于比较两个字符串的内容是否相等。
package com.itheima.demo1;
import java.util.Scanner;
public class ArithmeticoperatorDemo1 {
public static void main(String[] args) {
// 1.键盘录入字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
//2.反转键盘录入的字符串
String result = new StringBuilder().append(str).reverse().toString();
//.3比较
if(str.equals(result)){
System.out.println("当前字符串是对称字符串");
}else{
System.out.println("当前字符串不是对称字符串");
}
}
}
2.
package com.itheima.demo1;
public class ArithmeticoperatorDemo1 {
public static void main(String[] args) {
//1.定义数组
int[] arr = {1, 2, 3, 4};
//2.定义一个接受字符数组的子函数,把arr放进去
String str = arrString(arr);
System.out.println(str);
}
public static String arrString(int[] arr) {
//3. new一个StringBuilder容器,用来存放字符串
StringBuilder sb = new StringBuilder();
sb.append("[");//将 [ 先加入到容器中
//4.循环遍历分割数组
for (int i = 0; i < arr.length; i++) {
//5.采用判断语句,如果加入到了最后一个字时,就i=length-1,才会不打印 , 逗号
//其他加入的时候,就带 ,逗号一加入进容器
if (i == arr.length - 1) {
sb.append(arr[i]);
} else {
sb.append(arr[i]).append(",");
}
}
//循环结束之后,填右边括号
sb.append("]");
//6.完成后返回sb的字符串类型
return sb.toString();
}
}
3.4.4 StringJoiner
对于有时候StringBuilder按照数组拼接字符串的时候就比较麻烦了,中间有的时候拼,有的时候不拼
StringJoiner概述:
StringJoiner对象的创建
StringJoiner的成员方法
1.add() 添加内容
delimiter:中间间隔的符号,prefix:开始的符合,suffix:结尾的符号
public static void main(String[] args) {
StringJoiner sj = new StringJoiner("-----");
//2.添加元素
sj.add("aaa").add("bbbb").add("ccc");
//3.打印结果
System.out.println(sj);
}
2.
public static void main(String[] args) {
StringJoiner sj = new StringJoiner(",","[","]");
//2.添加元素
sj.add("aaa").add("bbbb").add("ccc");
int len = sj.length();
System.out.println(len);//14
//3.打印结果
System.out.println(sj);//容器的类型
String str = sj.toString();
System.out.println(str);//字符串的类型
}
总结:
3.4.5 字符串原理
扩展底层原理1和2:字符串原理
扩展底层原理3:字符串拼接的底层原理
1.当等号的右边没有变量时:
在编译的时候,就会将"a"+ “b” +“c"拼接为"abc”
2.当等号的右边有变量时:
-
在jdk8以前:系统底层会自动创建一个StringBuilder对象,然后再调用其append方法完成拼接。
拼接后,再调用其toString方法转换为String类型,而toString方法的底层是直接new了一个字符串对象。 -
在jdk8之后:在编译时,会先预估字符串的长度并创建一个数组,再把数组变成一个字符串,这样就消耗性能了。
面试练习题:
2.
那就采用StringBuilder 和StringJoiner 进行优化
扩展底层原理5:StringBuilder提高效率原理
stringBuilder是一个内容可变的容器,所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存
3、4原理小结:
扩展底层原理5:StringBuilder源码分析
总结:
ctrl + n IDEA快捷键 出现搜索界面
在所有位置里寻找,搜索界面默认在当前项目文件里找不到
--------->
ctrl + f12 ---->寻找toString
3.4.6 字符串综合练习
2.
3.
4. 集合
集合是一个容器,可以用来存储数据,和数组不同的是,集合的长度可以发生变化。当添加元素时,会自动扩容。
- 集合可以存储引用数据类型,不能直接存储基本数据类型,要把基本数据类型变成其对应的包装类才能存储。
4.1 ArrayList 集合(之一)
ArrayList集合的创建:
public static void main(String[] args) {
//1.创建集合的对象
//泛型:指限定集合中存储数据的类型,就是 <里面的>
//ArrayList<String> list = new ArrayList<String>();
// jDK7之后:
ArrayList<String> list = new ArrayList<>();
System.out.println(list);
//此时我们创建的是ArrayList的对象,
//这个类在底层做了一些处理
//打印对象不是地址值,而是集合中存储数据内容 也就是[]
//在展示的时候会拿[]把所有的数据进行包裹
}
ArrayList成员方法
- add()和remove的返回值都是布尔值,add默认返回true,不用管,而remove在删除成功时返回true,失败时返回false
同时,remove还可以根据索引进行删除,返回值是被删除的元素 - set是修改指定索引上的值,返回被覆盖的值。
- get就是获取相应索引的元素
- size就是集合的长度,类似于length
public static void main(String[] args) {
//1.创建集合的对象
ArrayList<String> list = new ArrayList<>();
//2.添加元素
list.add("aaa");
list.add("aaa");
list.add("www");
list.add("ddd");
System.out.println("2:"+list);
//3.删除元素
boolean result1 = list.remove("aaa");
System.out.println("3:"+result1);//当要删除的元素不存在时,就会false
System.out.println("3:"+list);
//把被删除的元素进行返回
String str = list.remove(2);
System.out.println("3:"+str);
System.out.println("3:"+list);
//4.修改元素
String res = list.set(1, "cccc");//把替换的元素进行返回
System.out.println("4:"+res);
System.out.println("4:"+list);
//5.查询元素
String s = list.get(0);
System.out.println("5:"+s);
System.out.println("5:"+list);
//6.遍历元素 list.fori
for (int i = 0; i < list.size(); i++) {
//list.get(i) 元素
String s1 = list.get(i);
System.out.println("6:"+s1);
System.out.println("6:"+list);
}
}
4.2 集合综合练习
4.2.1 添加字符串和整数遍历
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
//遍历
System.out.print("[");
for (int i = 0; i < list.size(); i++) {
if(i==list.size()-1){
System.out.print(list.get(i));
}else{
System.out.print(list.get(i)+",");
}
}
System.out.println("]");
}
2.
下方遍历代码一样,中间改变
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
添加字符类型遍历也是同理
ArrayList<Character> list = new ArrayList<>();
list.add('a');
list.add('b');
list.add('c');
list.add('d');
4.2.2 基本数据类型的包装类
这里的包装类实际上就是泛型里的关键字
jdk5以后int Intleger 之间是可以互相转化的
4.2.3 添加学生对象并遍历
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
//2.创建学生对象
Student s1 = new Student("zhangsan",23);
Student s2 = new Student("magua",24);
Student s3 = new Student("xiaolaoban",25);
//3.添加元素
list.add(s1);
list.add(s2);
list.add(s3);
//4.遍历集合
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println(stu.getName()+","+stu.getAge());
}
}
2.
遍历思路:
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
// 长度为0
//2.键盘录入学生的信息并添加到集合当中
Scanner sc = new Scanner(System.in);
for (int i = 0; i < 3; i++) {
Student s = new Student();
System.out.println("请输入学生的姓名:");
String name = sc.next();
System.out.println("请输入学生的年龄:");
int age = sc.nextInt();
//把name和age赋值给学生对象
s.setName(name);
s.setAge(age);
//把学生对象添加到集合当中
list.add(s);
}
//集合里面是否有学生对象
//有? 没有?
System.out.println(list.size());//3
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println(stu.getName()+","+stu.getAge());
}
}
4.2.4 添加用户对象并判断是否存在
javabean部分:
package test10;
public class User {
private String id;
private String username;
private String password;
public User() {
}
public User(String id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Test部分:
package test10;
import java.util.ArrayList;
public class test {
public static void main(String[] args) {
//1.创建集合
ArrayList<User> list = new ArrayList<>();
//2.创建三个用户对象
User u1 = new User("magua001","zhangsan","12345");
User u2 = new User("magua002","lisi","12345678");
User u3 = new User("magua003","wangwu","12345asd");
//3.把用户对象添加到集合当中
list.add(u1);
list.add(u2);
list.add(u3);
//4.调用方法查看id是否存在
boolean flag = contains(list, "magua001");
System.out.println(flag);
}
public static boolean contains(ArrayList<User> list,String id){
// for (int i = 0; i < list.size(); i++) {
// User u = list.get(i);
// String uid = u.getId();
// if(uid.equals(id)){
// //如果找到了直接返回true
// return true;
// }
// }
// //当循环结束表示集合里面所有的元素都比较完毕还没有一样的
// //那么就返回false
// return false;
return getIndex(list,id)>=0;
}
public static int getIndex(ArrayList<User>list,String id){
for (int i = 0; i < list.size(); i++) {
User u = list.get(i);
String uid = u.getId();
if(uid.equals(id)){
return i;
}
}
return -1;
}
}
4.2.5 添加手机对象并返回要求的数据
phone的javabean:
package test11;
public class Phone {
private String brand;
private int price;
public Phone() {
}
public Phone(String brand, int price) {
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
test部分:
public static void main(String[] args) {
//1.创建集合
ArrayList<Phone> list = new ArrayList<>();
//2.创建三个用户对象
Phone p1 = new Phone("xiaomi",1000);
Phone p2 = new Phone("iphone",4000);
Phone p3 = new Phone("cuizi",2000);
//3.把用户对象添加到集合当中
list.add(p1);
list.add(p2);
list.add(p3);
//4.调用方法
ArrayList<Phone> phoneInfoList = getPhoneInfo(list);
//5.遍历集合
for (int i = 0; i < phoneInfoList.size(); i++) {
Phone phone = phoneInfoList.get(i);
System.out.println(phone.getBrand()+","+phone.getPrice());
}
}
//1.我要干嘛?查询手机信息
//2.我干这件事情,需要什么才能完成?集合
//3.我干完了,方法的调用处是否需要继续使用结果?返回
//技巧:
//如果我们要返回多个数据,可以把这些数据先放到一个容器当中,再把容器返回/集合数组
public static ArrayList<Phone> getPhoneInfo(ArrayList<Phone> list){
//定义一个集合用于存储价格低于3000的手机对象
ArrayList<Phone> resultList = new ArrayList<>();
//遍历集合
for (int i = 0; i < list.size(); i++) {
Phone p = list.get(i);
int price = p.getPrice();
//如果当前手机的价格低于3000,
if(price<3000){
resultList.add(p);
}
}
return resultList;
//返回的是集合,函数的返回值类型就得是集合
}
5. 学生管理系统
学会阅读需求文档,在了解需求文档后,自己画出需求的结构流程图(业务流程图)
流程图:
Ctrl + alt + t 全选中内容后,快捷生成结构环绕方式
StudentTest第一部分:
package test1;
import java.util.ArrayList;
import java.util.Scanner;
public class StudentTest {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
while (true) {
//不要把ArrayList<Student> list = new ArrayList<>();
//放在循环内部,这样在添加学生到list后,再循环一次,list又变成了0,这样查询的时候就总是无法查到
System.out.println("----------------欢迎来到小黑子的学生管理系统----------------");
System.out.println("1:添加学生");
System.out.println("2:删除学生");
System.out.println("3:修改学生");
System.out.println("4:查询学生");
System.out.println("5:退出");
System.out.println("请输入您的选择:");
Scanner sc = new Scanner(System.in);
//定义一个字符串来接收
String choose = sc.next();
switch (choose) {
case "1" -> addStudnet(list);
case "2" -> deleteStudnet(list);
case "3" -> updateStudnet(list);
case "4" -> queryStudnet(list);
case "5" -> {
System.out.println("退出");
System.exit(0);//停止虚拟机运行
}
default -> System.out.println("没有这个选项");
}
}
}
//添加学生
public static void addStudnet(ArrayList<Student> list) {
//利用空参构造先创建学生对象
Student s = new Student();
Scanner sc = new Scanner(System.in);
String id = null;
while (true) {
System.out.println("请输入学生的id:");
id = sc.next();
boolean flag = contains(list, id);
if (flag) {
//true 表示id已经存在,需要重新录入
System.out.println("id已经存在,请重新录入");
} else {
//表示id不存在,唯一
s.setId(id);
break;
}
}
System.out.println("请输入学生的姓名:");
String name = sc.next();
s.setName(name);
System.out.println("请输入学生的年龄:");
int age = sc.nextInt();
s.setAge(age);
System.out.println("请输入学生的家庭住址:");
String address = sc.next();
s.setAddress(address);
//把学生对象添加到集合当中
list.add(s);
//提示用户
System.out.println("学生信息添加成功");
System.out.println(list.size());
}
//删除学生
public static void deleteStudnet(ArrayList<Student> list) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要删除的id:");
String id = sc.next();
//查询id在集合中的索引
int index = getIndex(list, id);
//对index进行判断
//如果-1,就表示索引不存在,结束方法,返回初始菜单
if (index >= 0) {
//如果大于等于0的,表示存在,直接删除
list.remove(index);
System.out.println("id为:" + id + "的学生删除成功");
} else {
System.out.println("id不存在,删除失败");
}
}
//修改学生
public static void updateStudnet(ArrayList<Student> list) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要修改学生的id:");
String id = sc.next();
int index = getIndex(list,id);
if(index == -1){
System.out.println("要修改的id:"+id+"不存在,请重新输入");
return;
}
//当代码执行到这里,表示什么?表示当前id是存在的
//获取要修改的学生对象
Student stu = list.get(index);
//输入其他的信息并修改
System.out.println("请输入修改的学生姓名:");
String newName = sc.next();
stu.setName(newName);
System.out.println("情输入修改的学生年龄:");
String newAge = sc.next();
stu.setName(newAge);
System.out.println("情输入修改的学生家庭住址:");
String newAddress = sc.next();
stu.setName(newAddress);
System.out.println("学生信息修改完成");
}
//查询学生
public static void queryStudnet(ArrayList<Student> list) {
System.out.println(list.size());
if (list.size() == 0) {
System.out.println("当前无学生信息,请添加后再查询");
return;
}
//打印表头信息
System.out.println("id\t\t姓名\t年龄\t家庭住址");
//当代码执行到这里时表示集合是有数据的,遍历集合的每个数据
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println(stu.getId() + "\t" + stu.getName() + "\t" + stu.getAge() + "\t" + stu.getAddress());
}
}
//判断id 的方法
public static boolean contains(ArrayList<Student> list, String id) {
// //循环遍历每一个学生对象
// for (int i = 0; i < list.size(); i++) {
// //拿到学生对象后,获取id并进行判断
// Student stu = list.get(i);
// String sid = stu.getId();
// if(sid.equals(id)){
// //存在,true
// return true;
// }
// }
// //不存在方法就返回false
// return false;
return getIndex(list, id) >= 0;
}
//通过id获取索引的方法
public static int getIndex(ArrayList<Student> list, String id) {
//遍历集合
for (int i = 0; i < list.size(); i++) {
//得到每一个学生对象
Student stu = list.get(i);
//得到每一个学生对象的id
String sid = stu.getId();
//拿着集合中的学生id跟要查询的id进行比较
if (sid.equals(id)) {
//如果一样,那么就返回索引
return i;
}
}
return -1;
}
}
swich语句不能用箭头表达式,java版本换成jdk17以上行了。
5.1 学生管理系统升级版
需求图:
流程图:
1.注册
2.登录
3.忘记密码
6. 面向对象进阶
6.1 static
6.1.1 static-静态变量
static(静态变量)(静态变量被该类所有对象共享),是Java中的一个修饰符,可以修饰成员方法,成员变量
javabean类:
public class Student {
private String name;
private int age;
private String gender;
public static String teacherName;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
//行为
public void study(){
System.out.println(name+"正在学习");
}
public void show(){
System.out.println(name+","+age+","+gender+","+teacherName);
}
测试类:
public static void main(String[] args) {
//1.创建学生对象
Student s1 = new Student();
s1.setName("张三");
s1.setAge(23);
s1.setGender("man");
s1.teacherName = "麻瓜";
s1.study();
s1.show();
//2.创建第二个学生对象
Student s2 = new Student();
s2.setName("李四");
s2.setAge(25);
s2.setGender("man");
s2.study();
s2.show();
}
当这里的teacherName被赋值后,static会被Student类里所有对象共享(公共的一个属性)
静态变量是随着类的加载而加载的,优先于对象出现,被赋值是单独存储再堆内存中的静态区域
示意图:
静态区里的变量是对象共享的,在内存中只有一份,谁用谁拿,非静态的变量是每一个对象所独有的,每一个对象里的非静态的变量都是单独存放的。
判断一个变量是否是静态变量,主要就是两个字,共享
需要共享的变量就是静态变量
6.1.2 static-静态方法和工具
静态方法
工具类
工具类:帮助我们做一些事情的,但是不描述任何事物的类
目前一共学习了三种类
工具类的要求:
这里的第2个私有化构造方法
就是
public class Student {
private public Student() {
}
}
一个类一旦被私有化构造方法后,就不能创建这个类的对象了。
练习:
1.
javabean类:
public class ArrayUtil {
//私有化构造方法
//目的:为了不让外界创建他的对象
private ArrayUtil(){}
public static String printArr(int[] arr){
//一到字符串的拼接,就一定要想到StringBuilde
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if(i==arr.length-1){
sb.append(arr[i]);
}else{
sb.append(arr[i]).append(", ");
}
}
sb.append("]");
return sb.toString();
}
public static double getAverage(double[] arr){
double sum =0;
for (int i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
return sum/arr.length;
}
}
测试类:
public class TestDemo {
public static void main(String[] args) {
//测试工具类的两个方法是否正确
int[] arr1 = {1, 2, 3, 4, 5};
String str = ArrayUtil.printArr(arr1);
System.out.println(str);
double[] arr2 ={1.5,23.43,654.43,1.23,23.3};
double avg = ArrayUtil.getAverage(arr2);
System.out.println(avg);
}
}
2.
工具类:
import java.util.ArrayList;
public class StudentUtil {
private StudentUtil(){}
//静态方法
public static int getMaxAgeStudent(ArrayList<Student> list){
//1.定义一个参照物
int max = list.get(0).getAge();
//2.循环遍历集合
for (int i = 0; i < list.size(); i++) {
//i 索引 list.get(i)元素/学生对象 我们还需要getAge获取到年龄之后再进行比较
int tempAge = list.get(i).getAge();
if(tempAge>max){
max = tempAge;
}
}
//3.直接返回
return max;
}
}
Test部分调用工具类方法:
//4.调用工具类方法
int maxAgeStudent = StudentUtil.getMaxAgeStudent(list);
System.out.println(maxAgeStudent);
6.1.3 static-注意事项
在非静态方法中,有一个隐藏的this,这个this就表示当前方法调用者的地址值
在第一次调用show()方法时,虚拟机就会把第一次调用者的地址赋值给方法中的this,就像图中所示
第二次同理,虚拟机会把第二次调用者的地址赋值给方法中的this
this的值
this的这一特性就是区分不同对象的 即在调用成员变量时,变量前面有隐含的this,从而显示出不同变量的结果
上面sout里的this可以省略
- 而在static静态方法中,是没有这个this关键字的
- 原因就是静态方法是公用的,而this一般是独立的对象进行使用,不符合公用的理念。
示意图:
- 静态方法不能访问非静态成员变量(也就是实例变量即对象)
因为在使用静态方法时,通过类名调用会直接到静态存储位置去找变量,而非静态的变量是随着对象的创建而创建的,在静态存储位置找不到,所以静态方法不能调用非静态成员变量
- 静态方法不能访问非静态的成员方法
因为非静态的方法需要调用者,而在静态方法中是没有this的,也就没有调用者,自然就不能使用了
综上,静态方法只能调用静态的内容
- 非静态方法可以访问所有
因为静态的是公用的,所以非静态既可以访问到静态,也可以访问非静态
小结:
6.1.4 重新认识main方法
JVM:虚拟机
例子:
点击IDEA右上角的锤子旁边的按钮
在程序参数里填写,args即可返回值
6.2 继承
6.2.1 封装
封装:
对象代表什么,就得封装对应的数据,并提供数据对应的行为
像这种相同的行为太多了,这时候就需要用到封装
封装完行为方法之后,就要用到继承了
6.2.2 继承的描述
继承实际上就是类跟类之间的父子关系
什么时候用继承?
小结:
继承需要学习的点:
6.2.3 继承的特点
即儿子继承父亲,父亲继承爷爷
即子类的父类是直接父类,父类的父类是子类的间接父类
java中有一个祖宗类就是Object,每一个类都直接或间接继承于Object
小结:
练习:
设计继承体系时,尽量先搞清楚体系逻辑,画一张图,从子类到父类
设计的核心就是:共性的内容抽取,子类是父类的一种
书写代码时,从父类到子类
小结:
6.2.4 子类继承父类的特性
-
构造方法:不论私有(private)还是非私有(像public等)的构造方法,子类都是不能继承的
例子:
-
成员变量:不论私有(private,不能直接调用)还是非私有(像public等)的成员变量,子类都是可以继承的(相当于从父类哪里copy了一份过来)
1.没有用private修饰的继承成员变量的内存图:
2.有用private修饰的继承成员变量的内存图:
- 这里new出来一个子类后,会把堆内存中开辟出来的空间分为两部分,第一部分是继承父类的成员变量,第二类是子类自己的成员变量
- 如果父类的成员变量使用private修饰后,子类对象在堆内存中就无法使用继承父类的成员变量。
父类会把非private,非static和非final的方法(这些方法统称为虚方法)抽取出来,放到虚方法表里,在继承的时候,父类会把虚方法表交给子类复制,子类可以添加新的虚方法加入到子类的虚方法表中,使后代继续复制
- 成员方法:非私有的成员方法(虚方法)可以被继承,私有的成员方法就不能被继承下来
运行效率低下:
以下如此可以大大提高性能:
例子:
6.2.5 终端的jps和jhsdb hsdb
在终端中输入jps,得到类中获得的id
输入jhsdb hsdb 打开HotSpot Debugger
- (HSDB(Hotspot Debugger) ,是一款内置于 SA 中的 GUI 调试工具,可用于调试 JVM运行时数据,从而进行故障排除。)
在file中寻找到第一个点击进入
从jps中获得的id输入,ok
之后显示
在tools里找到Memory Viewer
在tools里找到Inspector
6.2.6 继承中成员变量和成员方法的访问特点
继承成员变量
- 继承的成员变量的访问特点: 就近原则:
谁离我近,我就用谁
即优先级为: 方法内的成员变量>子类的成员变量>父类的成员变量
在使用一个成员变量时,会先在局部的方法内找,如果方法内没有,就会到本类(子类)中去找,本类中再没有,就会到父类中去找
在上述代码中,name就是“ziShow”,如果“ziShow”没有,name就是“Zi”,如果“Zi”再没有,那么name就是“Fu”
当然,也可以同时访问这三个成员变量
例子:
1.
public class Test {
public static void main(String[] args) {
Zi zi=new Zi();
zi.ziShow();
}
}
class Fu{
String name="Fu";
}
class Zi extends Fu{
String name="Zi";
public void ziShow(){
String name="ziShow";
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
2.
即使用this关键字可以访问本类的成员变量,使用super关键字可以访问父类的成员变量。
语法:
- this.变量名
- super.变量名
注:在子类中最多使用一个super
小结:
继承成员方法
继承的成员方法的访问特点:
- 直接调用:满足就近原则,谁近调用谁
- super调用:直接访问父类
与上面成员变量的特点一样,都是就近原则,都是this,super
public class Test {
public static void main(String[] args) {
Zi zi=new Zi();
zi.show();
}
}
class Fu{
String name="Fu";
public void eat(){
System.out.println("chifan,父类被调用");
}
}
class Zi extends Fu{
String name="Zi";
//这里进行了方法的重写,由于父类的方法不能满足子类的需求,从而进行了方法的重写
@Override
public void eat(){
System.out.println("heshui,子类被调用");
}
public void show(){
eat();
this.eat();
super.eat();
}
}
方法重写的本质:
新的方法覆盖旧的方法(虚方法表)
方法重写注意事项和要求:
私有方法不能进行重写,重写方法时也可以调用父类的方法
练习:
总结:
6.2.6 继承中的构造方法
- 特点
1.父类中的构造方法不会被子类继承
2.子类中所有的构造方法默认先访问父类中的无参构造,再执行自己
第二条出现的原因
例子:
所以,子类的构造方法的第一句都会是
super()
,即使不写也会存在于第一行。 这里的super()
就是调用父类的空参构造方法
如果想调用父类的有参构造,就必须手动写super进行调用
小结:
6.2.7 this、super关键字总结
- this 就相当于一个局部变量,表示当前方法调用者的地址值
- super 就代表父类的存储空间
this与super的区别:
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员变量 访问本类成员变量 | this.成员方法访问本类成员方法 | this(…)访问本类构造方法 |
super | super.成员变量 访问父类成员变量 | super. 访问父类成员方法 | super(…)访问父类构造方法 |
当使用this方法来访问本类构造方法:
public class Person {
String name;
int age;
String gender;
public Person() {
this(null,0,"男");
}
public Person(String name,int age,String gender){
this.name=name;
this.age=age;
this.gender=gender;
}
}
这里空参构造里的this(null,0,"男")
;就是使用this来访问构造方法,并赋予初始值
且,使用this()
后,虚拟机就不会自动添加super()
了
练习:
Employeer:
public class Employee {
//1.类名见名知意
//2.所有的成员变量都需要私有
//3.构造方法(空参带全部参数的构造)
//4.get/ set
private String id;
private String name;
private double salary;
public Employee(){}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//工作
public void work(){
System.out.println("员工在工作");
}
//吃饭
public void eat(){
System.out.println("吃米饭");
}
}
Manager:
public class Manager extends Employee{
private double bouns;
//空参构造
public Manager(double bouns) {
this.bouns = bouns;
}
//带全部参数的构造
//父类+子类
public Manager(String id, String name, double salary, double bouns) {
super(id, name, salary);
this.bouns = bouns;
}
public double getBouns() {
return bouns;
}
public void setBouns(double bouns) {
this.bouns = bouns;
}
@Override
public void work() {
System.out.println("管理其他人");
}
}
Cook:
public class Cook extends Employee{
public Cook(){}
public Cook(String id, String name, double salary) {
super(id, name, salary);
}
@Override
public void work() {
System.out.println("厨师正在炒菜");
}
}
Test:
public class Test {
public static void main(String[] args) {
//创建对象并赋值调用
Manager m = new Manager("magua001","小笼包",15000,8000);
System.out.println(m.getId()+","+m.getName()+
","+m.getSalary()+","+m.getBouns());
m.work();
m.eat();
Cook c = new Cook();
c.setId("magua002");
c.setName("lisi");
c.setSalary(8000);
System.out.println(c.getId()+","+c.getName()+","+c.getSalary());
c.work();
c.eat();
}
}
6.3 认识多态
多态的概念:
封装是多态的必要条件,继承是多态的前提条件
多态的应用场景:
不同的用户对象,采用同样的代码模块会出现冗余
解决方案:
多态的含义
多种形态,同种类型的对象表现出的不同形态 可以以父类来创建子类对象,即使用父类型作为参数,可以接收所有子类对象
多态语法
父类类型 对象名称=子类对象
多态可以根据调用对象的不同,使用对应的方法
例如
Person man=new Student()
,这里Person是父类,Student是子类 但是多态的使用要满足以下前提
例子:
Person类:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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;
}
public void show(){
System.out.println(name+","+age);
}
}
Student类:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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;
}
public void show(){
System.out.println(name+","+age);
}
}
Teacher类:
public class Teacher extends Person{
@Override
public void show() {
System.out.println("老师的信息为:"+getName()+","+getAge());
}
}
Adminnistrator类:
public class Administrator extends Person{
@Override
public void show() {
System.out.println("管理员的信息为:"+getName()+","+getAge());
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s =new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("麻瓜");
t.setAge(30);
Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
//不管传递什么对象,都可以被下方Person接收
//这就是多态的原因
register(s);
register(t);
register(admin);
}
//这个方法既能接收老师,又能接收学生,还能接收管理员
// 只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
总结:
6.3.1 多态中调用成员的特点
父类 对象名=new 子类()
遵循两个口诀:
-
编译看左边的解释: javac编译代码时,会看左边的父类有没有这个变量或方法,如果有,编译成功,如果没有,编译失败,即必须要有父类的方法
-
运行也看左边的解释: javac运行代码的时候,实际获取的就是左边父类中成员变量的值。
即对象调用遵循以上两个规则。
同理,方法调用也是如此,只不过在运行时会运行子类的方法,即右边
内存图解:
例子:
//父类Person
package com.inherit.study;
public class Person {
String name="人";
int age;
String gender;
public Person() {
}
public Person(String name,int age,String gender){
this.name=name;
this.age=age;
this.gender=gender;
}
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void show(){
System.out.println(name+" "+age);
}
}
//子类Student
package com.inherit.study;
public class Student extends Person {
String name="学生";
@Override
public void show(){
System.out.println("学生的信息为:"+getName()+" "+getAge());
}
}
//测试类
package com.inherit.study;
public class test3 {
public static void main(String[] args) {
Person pr=new Student();
System.out.println(pr.name);
pr.show();
}
}
可以看到,父类的成员变量显示出来,子类的成员变量没有显示,并且子类的方法被运行,父类的方法没有运行
子类的方法能运行的原因是子类重写的方法是虚方法,在虚方法表里覆盖了父类的虚方法。
如果是子类 对象名=子类()
这种形式调用成员变量时,会先在子类的内存空间寻找成员变量,子类的内存空间没有时,才会到父类的内存空间去找,这是继承的特性,而在多态中,父类 对象名=new 子类()
如果父类里没有相应的成员变量,会直接报错。
6.3.2 多态的优势和弊端
优势:
即只需把后面标红的代码修改就可以了
弊端:
- 不能调用子类的特有功能
- 因为子类的特有方法不是重写的,是子类独有的,父类里没有,所以不能调用,会报错。
解决方案:
用强制类型转换把父类型转换为子类型,不能随便转换,要对应着转换
例子:
public class Test {
public static void main(String[] args) {
//创建对象
Animal a =new Dog();
a.eat();
//多态的弊端
//不能调用子类的特有功能很错的原因?
//当调用成员方法的时候,编译看左边,运行看右边
//那么在编译的时候会先检查左边的父类中有没有这个方法,如果没有直接报错。
//a.lookHome();Animal没有lookHome() 报错
//解决方案:
//变回子类就可以了
//Dog d =(Dog) a;
//d.lookHome();
//细节:转换的时候不能瞎转,如果转成其他类的类型,就会报错
//Cat c = (Cat) a;
//c.CatchMouse(); 已经new成狗了,就不能再变成猫
}
}
class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void lookHome(){
System.out.println("狗看家");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃小鱼干");
}
public void CatchMouse(){
System.out.println("猫抓老鼠");
}
}
6.3.3 instanceof 判断是不是相应类型
可以用instanceof
关键字来判断是不是相应类型
对象名 instanceof
类
如果是相应类型,会返回true,否则返回false
//可以利用判断语句进行判断类型是否转换
if(a instanceof Dog){
Dog d =(Dog) a;
d.lookHome();
} else if(a instanceof Cat){
Cat c = (Cat) a;
c.CatchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
java14的新特性
对象名 instanceof 类 变量名
如果是相应类型,会直接变成相应类型的变量,否则结果是false
if(a instanceof Dog d){
d.lookHome();
} else if(a instanceof Cat c){
c.CatchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
小结:
6.3.4 多态综合练习
Animal类:
public class Animal {
private int age;
private String color;
public Animal() {
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public void eat(String something){
System.out.println("动物在吃"+something);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Dog类:
public class Dog extends Animal{
public Dog(){}
public Dog(int age,String color){
super(age,color);
}
//行为
//eat(String something ) (something表示吃的东西)
//看家lookHome方法(无参数)
@Override
public void eat(String something) {
System.out.println(getAge()+"岁的"+getColor()+"黑颜色的狗两只前腿死死的抱住"+something+"骨头猛吃");
}
public void lookHome(){
System.out.println("狗在看家");
}
}
Cat类:
public class Cat extends Animal{
public Cat() {
}
public Cat(int age, String color) {
super(age, color);
}
@Override
public void eat(String something) {
System.out.println(getAge()+"岁的"+getColor()+"黑颜色的狗两只前腿死死的抱住"+something+"骨头猛吃");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
Person类:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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;
}
// //饲养狗
// public void keePet(Dog dog,String something){
// System.out.println("年龄为"+age+"岁的"+name+"养了一只"+dog.getColor()+"颜色的"+dog.getAge()+"岁的狗");
// dog.eat(something);
// }
// //饲养猫
// public void keePet(Cat cat,String something){
// System.out.println("年龄为"+age+"岁的"+name+"养了一只"+cat.getColor()+"颜色的"+cat.getAge()+"岁的猫");
// cat.eat(something);
// }
//想要一个方法,能接收所有的动物,包括猫,包括狗
//方法的形参,可以写这些类的父类 Animal
public void keepPet(Animal a,String something){
if(a instanceof Dog d){
System.out.println("年龄为"+age+"岁的"+name+"养了一只"+a.getColor()+"颜色的"+a.getAge()+"岁的狗");
d.eat(something);
}else if(a instanceof Cat c){
System.out.println("年龄为"+age+"岁的"+name+"养了一只"+c.getColor()+"颜色的"+c.getAge()+"岁的猫");
c.eat(something);
}else{
System.out.println("没有这种动物");
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建对象并调用方法
// Person p = new Person("老王",30);
// Dog d = new Dog(2,"墨");
// p.keePet(d,"骨头");
//
// Person p2 = new Person("老李",25);
// Cat c = new Cat(4,"灰");
// p2.keePet(c,"鱼");
Person p = new Person("老王",30);
Dog d = new Dog(2,"黑");
Cat c =new Cat(3,"灰");
p.keepPet(d,"骨头");
p.keepPet(c,"鱼");
}
}
6.4 包和 final
6.4.1 包
包的起名规则:
公司域名反写+包的作用
包名加上类名才是全部的类名,下面的名字太长了
改进:如果想在一个包里使用其他包的类名,就需要引用使用的那个包的名字加上类名
import 包名.类名
这样就可以使用其他包的类了
注意事项:
使用两个包的同名类时,不需要导包了,直接使用同名类
小结:
6.4.2 final (与static类似的关键字)
在代码中,一旦被final修饰,就表示被修饰的内容不能改变了,有点像JavaScript的const。
final可以修饰方法,类,和变量
- 修饰方法时: 表示该方法是最终方法,不能被重写
- 修饰类时: 表示该类是最终类,不能被继承
- 修饰变量时: 这个变量就会变为一个常量,只能被赋值一次
使用final的场景:
如果定义的方法是一种规则时,可以用final 主要还是用final来修饰变量
常量:
主要是第二点,对象的内部可以改变。
字符串不可变的原因就是final和private
ieda快捷键 crtl+shift+u可以让字母变成大写
小结:
6.5 权限修饰符和代码块
6.5.1 权限修饰符
权限修饰符的分类:
有四种类,被访问的范围从小到大分为:private<空着不写(默认情况)<protected<public
修饰符 | 同一个类中的作用范围 | 同一个包里的其他类的作用范围 | 不同包下的子类的作用范围 | 不同包下的无关类的作用范围 |
---|---|---|---|---|
private | 可以 | 不行 | 不行 | 不行 |
空着不写(默认) | 可以 | 可以 | 不行 | 不行 |
protected | 可以 | 可以 | 可以 | 不行 |
public | 可以 | 可以 | 可以 | 可以 |
实际开发中,一般来讲,只会用private和public,且遵守以下规则:
6.5.2 代码块
代码块可以分为三类
局部代码块,构造代码块,静态代码块
- 局部代码块
作用:提前结束变量的生命周期(例如上面的a,代码块结束后,a就会消失)
这个技术现在已经用不到了
- 构造代码块(了解)
1.就是写在成员位置的代码块,可以优先于构造方法执行
2.作用:可以把多个构造方法中重复的代码抽取出来。
3.执行时机:我们在创建本类对象的时候会先执行构造代码块,再执行构造方法
这个技术现在也渐渐淘汰了
可以将重复的代码抽取成一个方法,构造方法需要使用时直接调用方法就行了。
- 静态代码块(重点)
就是在构造代码块的基础上加上static
小结:
6.6 抽象类和抽象方法
抽象类
下面每个子类的工作内容是不一样的,解决方法就是在子类中随便写一个方法乙,让子类再去重写。
但是有一个小弊端,下面的子类是别人写的代码,别人在写子类的时候忘了重写,那就没办法了。
所以,抽象关键字就来进行强制重写,否则代码就直接报错,抽象方法所在的类就叫抽象类。
抽象关键字:abstract
抽象类:
如果一个类中存在抽象方法,那么该类就必须声明为抽象类。(由方法推向类,不是由类推向方法)
抽象方法
抽象方法的定义格式:
public abstract 返回值类型 方法名(参数列表);
没有方法体
抽象类的定义同理:public abstract class 类名{}
抽象类和抽象方法的注意事项
1.抽象类不能创建对象
2.抽象类中不一定有抽象方法,有抽象方法的一定是抽象类
3.抽象类可以有构造方法(作用:当创建子类对象时给子类的属性进行赋值)
4.抽象类的子类分两种情况:
- 如果子类不是抽象类,则需要重写抽象类中的所有方法
- 如果子类是抽象类,则没事
ieda便捷键 alt+回车 可以查看报错的内容并且快速修改
练习:
Animal类:
public abstract class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(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;
}
public void drink(){
System.out.println("动物在喝水");
}
public abstract void eat();
}
Frog类:
public class Frog extends Animal{
public Frog() {
}
public Frog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("青蛙在吃虫子");
}
}
Dog类:
public class Dog extends Animal{
public Dog(){}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
Sheep类:
public class Sheep extends Animal{
public Sheep() {
}
public Sheep(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("山羊在吃草");
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建对象
Frog f =new Frog("mauga",1);
System.out.println(f.getName()+","+f.getAge());
f.drink();
f.eat();
}
}
抽象类和抽象方法的意义:
当每个关于同一个行为的代码不统一时,调用时就会非常痛苦
用抽象类进行重写
总结:
6.7 接口
接口就是一种规则,是指父类与子类之间的关系。
下述图片很好的说明了为什么要有接口:
接口的应用:
接口不代表一类事务,他代表一种规则,是对行为的抽象
接口与抽象的区别:
抽象更多是表示一类事物,而接口更多表示行为
接口的定义和使用:
1.接口用关键字interface
定义
public interface 接口名
{}
2.接口不能创建对象
3.接口和类是实现关系,通过inplements
关键字表示
public class 类名 inplements 接口名{}
4.接口的子类(接口的实现类)
分两种情况:
- 不是抽象类直接重写所有抽象方法(更多用这种)
- 是抽象类没事
5.接口里的方法都是抽象方法
练习:
创建新的类时选择接口的选项
Animal类:
public abstract class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(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;
}
public abstract void eat();
}
接口:
public interface Swim {
public abstract void swim();
}
Rabbit类:
public class Rabbit extends Animal{
public Rabbit() {
}
public Rabbit(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("兔子在吃胡萝卜");
}
}
Frog类:
public class Frog extends Animal implements Swim{
public Frog() {
}
public Frog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("青蛙在吃虫子");
}
@Override
public void swim() {
System.out.println("青蛙在蛙泳");
}
}
Dog类:
public class Dog extends Animal implements Swim{
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void swim() {
System.out.println("狗刨式");
}
}
测试类:
public class Test {
public static void main(String[] args) {
Frog f = new Frog("青蛙",1);
System.out.println(f.getName()+","+ f.getAge());
f.eat();
f.swim();
Rabbit r = new Rabbit("兔子",2);
System.out.println(r.getName()+","+r.getAge());
r.eat();
}
}
6.7.1 接口的细节:成员特点和各种接口的关系
接口中成员的特点:
inter接口:
public interface Inter {
//public static final
int a =10;
//public abstract
void method();
}
InterImp1类:
public class InterImp1 implements Inter{
@Override
public void method() {
System.out.println("method");
}
}
测试类:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
//创建实现对象并调用方法
InterImp1 li = new InterImp1();
li.method();
Scanner sc = new Scanner(System.in);
sc.next();
//防止程序运行过快,无法反应
}
}
接口和类之间的关系:
- 类和类之间的关系
继承关系,只能单继承,不能多继承,但是可以多重继承 - 类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
多个接口中有重名的方法,只需在类里重写一次就行了 - 接口与接口之间的关系
继承关系,可以单继承,也可以多继承。
如果实现类实现了子接口,那么就需要重写所有的抽象方法
//如果实现类实现了子接口,那么就需要重写所有的抽象方法
//接口1
public interface Inter1{
public abstract void method1();
}
//接口2
public interface Inter2{
public abstract void method2();
}
//接口3
public interface Inter3 extends Inter1,Inter2{
public abstract void method3();
}
//实现类
public class InterImpl implements Inter3{
@Override
public void method1(){
}
@Override
public void method2(){
}
@Override
public void method3(){
}
}
总结:
6.7.2 接口和抽象类案例
流程图思路:
1.
2.最优流程图
//因为直接创建人的对象是没有意义的,所以直接把person创建为抽象类
public abstract class Person {
private String name;
private int age;
public Person() {
}
public Person(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;
}
}
---------------------------------------------------
package com.inherit4.study;
public abstract class athletes extends Person{
public athletes() {
}
public athletes(String name, int age) {
super(name, age);
}
public abstract void study();
}
---------------------------------------------------
package com.inherit4.study;
public abstract class instructor extends Person{
public instructor() {
}
public instructor(String name, int age) {
super(name, age);
}
public abstract void teach();
}
---------------------------------------------------
package com.inherit4.study;
public interface English {
public abstract void eng();
}
---------------------------------------------------
package com.inherit4.study;
public class ppathletes extends athletes implements English{
public ppathletes() {
}
public ppathletes(String name, int age) {
super(name, age);
}
@Override
public void eng() {
System.out.println("说英语");
}
@Override
public void study() {
System.out.println("学打乒乓球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class bkathletes extends athletes{
public bkathletes() {
}
public bkathletes(String name, int age) {
super(name, age);
}
@Override
public void study() {
System.out.println("学打篮球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class ppInstuctor extends instructor implements English{
public ppInstuctor() {
}
public ppInstuctor(String name, int age) {
super(name, age);
}
@Override
public void eng() {
System.out.println("说英语");
}
@Override
public void teach() {
System.out.println("教打乒乓球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class bkInstucttor extends instructor{
public bkInstucttor() {
}
public bkInstucttor(String name, int age) {
super(name, age);
}
@Override
public void teach() {
System.out.println("教打篮球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class test {
public static void main(String[] args) {
ppathletes pa=new ppathletes("zhangsan",18);
System.out.println(pa.getName()+" "+pa.getAge());
pa.eng();
pa.study();
bkathletes ba=new bkathletes("zhangsi",19);
System.out.println(ba.getName()+" "+ba.getAge());
ba.study();
ppInstuctor pi=new ppInstuctor("lisi",30);
System.out.println(pi.getName()+" "+pi.getAge());
pi.eng();
pi.teach();
bkInstucttor bi=new bkInstucttor("zhangliu",36);
System.out.println(bi.getName()+" "+bi.getAge());
bi.teach();
}
}
6.7.3 接口的新增方法
jdk7以前,接口中只能定义抽象方法
jdk8以后,接口中可以定义有方法体的方法(默认,静态)
jdk9以后,接口中可以定义私有方法
即jdk8之后,接口里的方法可以有方法体了
我又想加新的规则;又想让他们的代码不报错怎么办呢?
jdk8以后接口中新增的默认方法
允许在接口中定义默认方法,需要使用关键字default
修饰
作用:解决接口升级的问题
接口默认方法的定义格式:
public default 返回值类型 方法名(参数列表){方法体}
例如:
public default void show(){}
接口中默认方法的注意事项:
- 接口中的默认方法不是抽象方法,所以不强制重写。如果被重写,重写时去掉default 关键字。
- public可以省略,default不能省略
- 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。
接口A:
public interface InterA {
public abstract void method();
public default void show(){
System.out.println("InterA接口中的默认方法----show");
}
}
接口B:
public interface InterB {
public default void show(){
System.out.println("InterB接口中的默认方法----show");
}
}
InterImpl:
public class InterImpl implements InterA,InterB{
@Override
public void method() {
}
@Override
public void show() {
InterA.super.show();
}
}
测试类:
public class Test {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show();
}
}
jdk8以后接口中新增的静态方法
jdk8以后接口中新增的方法(静态方法)
静态方法不能重写。
允许在接口中定义静态方法,需要使用关键字static
修饰
接口静态方法的定义格式:
public static 返回值类型 方法名(参数列表){方法体}
例如
public static void show(){}
接口中静态方法的注意事项:
- 接口中的静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
重写的概念:
jdk9新增的私有方法
为什么在接口中增加私有方法?
对于一些重复代码,是对单独一个接口中其他方法去服务的,不想被外类去访问,因为外类访问是没有意义的,因此在接口中出现了私有方法
接口中私有方法的定义格式:
格式1:private 返回值类型 方法名(参数列表){}
(不加default关键字,给默认方法服务,即默认方法中的重复代码)
例如:
private void show(){}
格式2:private static 返回值类型 方法名(参数列表){}
(给静态方法服务,即静态方法中的重复代码)
例如:
private static void method(){}
小结:
6.7.4 接口应用
- 把多个类都有可能用到的规则定义成接口,对实现类来讲,想要要实现类拥有什么样的功能,就实现对应的接口就行了。
即接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就行了 - 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。
小结:
6.7.5 适配器设计模型
设计模式:
设计模式是一套被反复利用,多数人知晓的,经过分类编目的,代码设计经验的总结。使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性,程序的重用性。
简单理解:设计模式就是各种套路
适配器设计模式:
- 解决接口与接口实现类之间的矛盾问题
就是在接口和实现类中间加上一个单独的类,这个类把接口里的所有方法重写(空着),然后,实现类使用时只需要重写需要的方法就行了
注意,中间类的名字是XXXAdapder
接口:
中间类:
实现类:继承中间的适配器,不让外界创建它的对象
总结:
6.8 初始内部类
类的五大成员:
属性,方法,构造方法,代码块,内部类
内部类就是在一个类里再定义的类
例如:
public class Person{
public class Student{
}
}
为什么要学习内部类?
代码实现:
内部类的分类
分为:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
前三种了解一下哎,写代码时能看懂,会用就行,最后一种匿名内部类需要掌握
person类里的student就是内部类,person就是外部类
跟这两个类无关的其他所有类就是外部其他类
- 定义内部类的规则: 内部类表示的事物是外部类的一部分 内部类单独出现没有任何意义
- 内部类的访问特点: 内部类可以直接访问外部类的成员,包括私有的 外部类要访问内部类的成员,必须在外部类里创建对象
案例:
总结:
6.8.1 成员内部类
写在成员位置的内部类就是成员内部类
public class Car {
private String carName;
private int carAge;
private String carColor;
eng neng=new eng();
public void show2(){
System.out.println(neng.engAge+" "+neng.engName+" "+neng.engColor);
}
class eng{//这里就是成员内部类
String engName;
int engAge;
String engColor;
public void show1(){
System.out.println(carName+" "+carAge+" "+carColor);
}
}
}
- 成员内部类代码的书写
1.写在成员位置的,属于外部类的成员,有着成员的性质
2.成员内部类可以被一些修饰符所修饰,比如:private,默认,protected,public,static(用static修饰的内部类就不是成员内部类了,是静态内部类)
3.成员内部类里可以定义静态变量(jdk16以后)
成员内部类的创建
- 方式1:(private修饰时会使用)
在外部类中编写方法,对外提供内部类的对象,可以用Object创建一个先对象来接收内部类对象的返回值,也可以直接使用
注意版本要匹配:
方式1的例子:
public class Outer {
private String name;
private class Inner{
}
public Inner getInner(){//创建方法
return new Inner();
}
}
Outer o=new Outer();
o.getInner();//调用方法
- 方式2: 直接创建格式:外部类名.内部类名 对象名=外部类对象(new 对象).内部类对象(new 对象);
//这里相当于内部类是外部类的成员,创建外部类对象后调用
Outer.Inner duixiang= new Outer().new Inner();
//这里Outer是外部类名,Inner是内部类名
成员内部类获取外部类的成员变量(使用Outer.this.变量,Outer是外部类名)
练习:
内存图解析:
Outer类:
public class Outer {
private int a =10;
class Inner{
private int a =20;
public void show(){
int a = 30;
System.out.println(Outer.this.a);
System.out.println(this.a);
System.out.println(a);
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建内部类的对象,并调用show方法
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
小结:
6.8.2 静态内部类
例子:
public class Outer {
int a=10;
static String name="111";
//静态内部类
static class Inner{
public void show1(){
Outer oi=new Outer();
sout(oi.a);//外部类的非静态对象需要创建对象才能访问
sout(name);//外部类的静态变量可以直接访问
}
public static void show2(){
Outer oi=new Outer();
sout(oi.a);//外部类的非静态对象需要创建对象才能访问
sout(name);//外部类的静态变量可以直接访问
}
}
}
- 创建静态内部类的语法:
外部类名.内部类名 对象名=new 外部类名.内部类名();
例如:Outer.Inner oi=new Outer.Inner();
(这里new的是Inner) - 用静态内部类对象调用静态内部类中的非静态方法:
先创建对象,用对象调用 - 调用静态内部类中的静态方法:
直接调用:Outer.Inner.方法名()
案例:
public class Outer {
//静态内部类
static class Inner{
public void show1(){
sout(非静态的方法被调用了);
}
public static void show2(){
sout(静态的方法被调用了);
}
}
}
//调用
//调用静态内部类里的非静态的方法,需要先创建一个静态内部类对象,然后用对象调用
Outer.Inner oi=new Outer.Inner();
oi.show1();
//调用静态内部类里的静态的方法,可以直接通过 外部类.内部类.方法名() 调用
Outer.Inner.show2();
小结:
6.8.3 局部内部类
案例:
Outer类:
public class Outer {
int b=20;
public void show(){
int a=10;
//局部内部类
class Inner{
String name;
int age;
public void method1(){
System.out.println(a);
System.out.println(b);
System.out.println("局部内部类中的method1方法");
}
public static void method2(){
System.out.println("局部内部类中的method2静态方法");
}
}
//创建局部内部类的对象
Inner i = new Inner();
System.out.println(i.name);//null
System.out.println(i.age);//0
i.method1();
Inner.method2();
}
}
测试类:
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.show();
}
}
6.8.4 匿名内部类
匿名内部类的本质就是隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置
匿名内部类有三个特点:
1.实现或继承关系
2.方法的重写
3.创建对象
匿名内部类的语法:
new 类名或接口名(){
重写方法();//类或接口的抽象方法的重写
};
//这一段代码的返回值是一个对象,可以被对象变量接收
例如:
new Inter(){
public void show(){
}
};
即:
new Swim(){//这里的new创建的是后面没有名字的类的对象
@Override
public void swim(){
sout("重写的游泳方法");
}
};
/*这一部分从语法上来解释,应该叫做匿名内部类的对象
new Swim(){
@Override
public void swim(){
sout("重写的游泳方法");
}
};
*/
/*这一部分才是没有名字类的主体,即匿名内部类,实现了Swim这个接口
{
@Override
public void swim(){
sout("重写的游泳方法");
}
};*/
以上代码有三个特点
- 1.实现关系
- 2.方法的重写
- 3.创建对象
同理,当Swim换成一个类名时,特点就变为了
1.继承关系
2.方法的重写
3.创建对象
此时匿名内部类的主题就是父类的子类
new Fulei(){//这里的new创建的是后面没有名字的类的对象
@Override
public void show(){
sout("重写的show方法");
}
};
/*这一部分从语法上来解释,应该叫做匿名内部类的对象
new Fulei(){
@Override
public void show(){
sout("重写的show方法");
}
};
*/
/*这一部分才是没有名字类的主体,即匿名内部类,继承了Fulei的子类
{
@Override
public void show(){
sout("重写的show方法");
}
};*/
应用场景
可以作为方法的实参,不用再写格外的类
比如:
//Animal类(父类)
public abstract class Animal{
public void eat(){
sout("吃东西")
}
}
//Dog类(子类)
public class Dog extends Animal{
@Override
public void eat(){
sout("狗吃骨头")
}
}
//测试类
psvm(String[] args){
//以前调用method方法
Dog d=new Dog();
method(d);
//使用匿名内部类调用method方法
method(
new Animal(){
@Override
public void eat(){
sout("狗吃骨头")
}
}
)
/*
new Animal(){
@Override
public void eat(){
sout("狗吃骨头")
}
}
这个整体就相当于:Animal a=子类对象 ,即多态
*/
public static void method(Animal a){
a.eat();
}
}
拓展:
可以直接用语法的形式调用对象
同理
new Fulei(){//这里的new创建的是后面没有名字的类的对象
@Override
public void show(){
sout("重写的show方法");
}
}.show();//所以可以调用自己里面的方法
小结: