day7
继承
子类(继往开来!更厉害!是扩展)的共性代码都继承自父类,每个字了只要写自己特有的代码即可。
作用:
- 继承的出现提高了代码的复用性;
- 继承的出现让类与类之间产生了关系,提供了多态的前提;
- 不要仅为了获取其他类中的某个功能而去继承
子类继承了父类,就继承了父类的方法和属性;
在子类中,可以使用父类中定义的方法和属性,要可以创建新的数据和方法;
在java中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类扩展。
这里通过setter和getter怎么弄?
单继承和多层继承
练习
父类,ManKind类
package course02;
public class ManKind {
int sex;
int salary;
public int getSex(){
return sex;
}
public void setSex(int sex){
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public void manOrWomen(){
if (this.sex == 1){
System.out.println("no job");
}else if(this.sex == 0){
System.out.println("women");
}
}
public void employeed(){
if (this.salary == 0){
System.out.println("no job");
}else{
System.out.println("job");
}
}
}
子类,Kids类
package course02;
public class Kids extends ManKind{
int yearsOld;
public int getYearsOld() {
return yearsOld;
}
public void setYearsOld(int yearsOld) {
this.yearsOld = yearsOld;
}
public void printAge(){
System.out.println(this.yearsOld);
}
public static void main(String[] args) {
Kids somekid = new Kids();
somekid.setSex(1);
somekid.setSalary(100);
System.out.println(somekid.getSex()); // 1
System.out.println(somekid.getSalary()); // 100
somekid.manOrWomen(); // no job
somekid.employeed(); // job
somekid.printAge(); // 0
}
}
方法的重写
定义:在子类中可以根据需要对父类中继承来的方法进行改造,也称方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求:
- 重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型;
- 重写方法不能使用比被重写方法更严格的访问权限;
- 重写和被重写的方法必须同时为static的,或同时为非static的
- 子类方法抛出的异常不能大于父类被重写方法的异常
子类重写父类的方法,只是重新编写方法体的代码;如果父类的方法是public的,子类重写的时候就不能使用缺省以下。
父类person类
public class Person {
int age;
String name;
int sex;
public void showInfo(){
System.out.println(this.age);
System.out.println(this.name);
System.out.println(this.sex);
}
// public void setInfo(int age, String name, int sex){
// this.age = age;
// this.name = name;
// this.sex = sex;
// }
}
子类student类
public class Student extends Person{
@Override
public void showInfo() {
super.showInfo();
System.out.println("以下是student类对person类的showInfo方法的重写");
System.out.println(this.age);
System.out.println(this.name);
System.out.println(this.sex);
}
public static void main(String[] args) {
Student stu = new Student();
stu.showInfo();
}
}
// 如果没有super.showInfo();则不会有以下三行。
//0
//null
//0
//以下是student类对person类的showInfo方法的重写
//0
//null
//0
如果现在父类的一个方法定义成private访问权限,在子类中将此方法声明为default访问权限,那么这样还叫重写吗?
不,🙅♀️。子类不能访问父类的私有的东西。
父子类与访问修饰符的关系
- 如果子类和父类在同一个包下,那么对于父类的成员修饰符只要不是私有的private,那子类就都可以使用;
- 如果子类和父类不在同一个包下,子类只能使用父类中protected和public修饰的成员。
关键字super
练习
1、
public class Kids extends ManKind{
int yearsOld;
public int getYearsOld() {
return yearsOld;
}
public void setYearsOld(int yearsOld) {
this.yearsOld = yearsOld;
}
public void printAge(){
System.out.println(this.yearsOld);
}
@Override
public void employeed() {
// super.employeed();
System.out.println("but kids should study and no job");
}
public static void main(String[] args) {
Kids somekid = new Kids();
somekid.setSex(1);
somekid.setSalary(100);
System.out.println(somekid.getSex()); // 1
System.out.println(somekid.getSalary()); // 100
somekid.manOrWomen(); // no job
somekid.employeed(); // but kids should study and no job
somekid.printAge(); // 0
}
}
子类调用父类的构造方法
- 子类中所有的构造方法默认都会访问父类中空参数的构造方法;
- 当父类中没有空参数的构造方法时,子类的构造方法必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造方法,且必须放在构造方法的第一行;
- 如果子类的构造方法中,既没有显示调用父类或者本类的构造方法,且父类中又没有无参的构造方法,则编译出错。
以下是第2.条的说明例子:
但是这个时候,mankind的子类kids就会报错
要使用super调用父类构造方法(默认会有)
子类如果要调用父类的构造器,必须使用super,父类若有参数,子类调用父类构造器时要置顶。
实例,父类mankind类
package course02;
public class ManKind {
int sex;
int salary;
// 显式的构造方法
public ManKind(int sex, int salary){
this.sex = sex;
this.salary = salary;
System.out.println("父类mankind的构造方法" + " sex:" + this.sex + " salary:" + this.salary);
}
public int getSex(){
return sex;
}
public void setSex(int sex){
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public void manOrWomen(){
if (this.sex == 1){
System.out.println("no job");
}else if(this.sex == 0){
System.out.println("women");
}
}
public void employeed(){
if (this.salary == 0){
System.out.println("no job");
}else{
System.out.println("job");
}
}
}
子类,注意!实例化子类对象时,先调用父类的构造方法,再调用子类的构造方法。
package course02;
public class Kids extends ManKind{
// 子类如果要调用父类的构造器,必须使用super,父类若有参数,子类调用父类构造器时要置顶。
public Kids(int sex, int salary){
super(sex, salary);
System.out.println("重写子类kids的构造方法"+ " sex:" + this.sex + " salary:" + this.salary);
}
int yearsOld;
public int getYearsOld() {
return yearsOld;
}
public void setYearsOld(int yearsOld) {
this.yearsOld = yearsOld;
}
public void printAge(){
System.out.println(this.yearsOld);
}
@Override
public void employeed() {
// super.employeed();
System.out.println("but kids should study and no job");
}
public static void main(String[] args) {
Kids somekid = new Kids(0,520);
somekid.setSex(1);
somekid.setSalary(100);
System.out.println(somekid.getSex()); // 1
System.out.println(somekid.getSalary()); // 100
somekid.manOrWomen(); // no job
somekid.employeed(); // but kids should study and no job
somekid.printAge(); // 0
}
}
//父类mankind的构造方法 sex:0 salary:520
//重写子类kids的构造方法 sex:0 salary:520
//1
//100
//no job
//but kids should study and no job
//0
关键字this和super的区别
多层继承,super访问的父级的方法
java对象的实例化过程
我们建立的类,存放在方法区,实例化类的时候,new一个对象,会存在堆里,并且栈里会有一个对象,指向new来的对象啊。
多态性
编译时和运行时类型
如果编译时和运行时类型不同,就出现了对象的多态。
对成员变量来说:
对象的多态——在java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可以指向(引用)多种不同类型的对象
堆 放的是new之后实际的对象。
e虽然指向子类对象,但是!
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
Student m = new Student(); m.school = "pku"; //合法,Student类有school成员变量 Person e = new Student(); e.school = "pku"; // 非法,Person类没有school成员变量,因而编译出错。
???以下的例子中,是可以访问子类的方法的呀?
解释,
Person e = new Student();
对象e不能再访问子类Student中新增的方法,但是可以访问子类Student重写的父类Person的方法。
举例:访问成员变量
所以!
对方法来说:
package polym;
public class Person {
public String name;
public int age;
public void showInfo(){
System.out.println("父类的showInfo方法");
System.out.println("姓名:" + this.name);
System.out.println("年龄:" + this.age);
}
public static void main(String[] args) {
Person p = new Person();
// p.getInfo();
Student s = new Student(); // 父类里,也可以实例化子类对象
// s.getInfo();
p.age = 50;
p.name = "erick";
p.showInfo();
}
}
package polym;
public class Student extends Person{
public String name;
public int age;
public String course;
public String interest;
public String school;
@Override
public void showInfo() {
System.out.println("子类student的showInfo方法");
System.out.println("姓名:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("课程:" + this.course);
System.out.println("兴趣:" + this.interest);
}
public void printInfo(){
System.out.println("访问子类中新添加的方法");
}
public static void main(String[] args) {
Student stu = new Student();
stu.name = "elle";
stu.school = "pku";
stu.age = 18; // 这样就可以赋值了
stu.printInfo();
// stu.showInfo();
/*
子类student的showInfo方法
姓名:elle
年龄:18
课程:null
兴趣:null
访问子类中新添加的方法
* */
// 父类对象引用子类对象
Person e = new Student();
e.name = "moko";
// e.school = "ui"; 报错
e.showInfo();
// e.printInfo(); // 该变量就不能再访问子类中添加的方法,报错
//子类student的showInfo方法
//姓名:null
//年龄:0
//课程:null
//兴趣:null
}
}
虚拟方法调用(virtual method invocation)
- 正常的方法调用
Person p = new Person();
p.getInfo();
Student s = new Student();
s.getInfo();
- 虚拟方法调用(多态情况下)
Person e = new Student();
e.getInfo(); //调用student类的getinfo()方法
- 编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
引用变量在栈里,new student对象在堆里。
动态绑定与内存有关系
总结:方法是运行时确定的,属性是编译时确定的!!(属性的话引用变量类型是什么就是什么类型,方法的话就要看引用对象的类型!)
多态小结
前提:
- 需要存在继承或者实现关系;
- 要有覆盖操作;
成员方法:
- (成员方法的多态性,也就是动态绑定,必须得存在于方法的重写之上);
- 编译时:要查看引用变量所属的类中是否有所调用的方法;
- 运行时:调用实际对象所属的类中的重写方法;
成员变量:
- 不具备多态性,只看引用变量所属的类。
子类所谓的覆盖,覆盖的是子类继承过来的方法,不会变更父类自身所设置的方法。
向下转型:子类特有功能要通过向下转型才能访问?
子类继承父类
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中;
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。
如果子类没有重写父类的方法,那么子类就是直接使用父类的方法;如果重写了,自己就使用自己重写之后的方法。
多态性应用举例
相当于 Person e = new Student();
instanceof 操作符
object 对象
例子:在我还不知道形参确定接收的是什么类的时候,可以设置成object。
object类中的主要方法
equals()
hashCode()
toString()
打印的是:当前对象所在的内存地址 21bcffb5
System.out.println(p.toString()); //polym.Person@21bcffb5
父类可以接受任何子类对象的实例
Object o = new Student();
day8
对象类型转换
String是个类!!!!
public class Test {
public static void main(String[] args) {
String s = "hello";
Object obj = s;
System.out.println(obj); //hello
}
}
对象类型转换举例
== 与equals
==是 i==3? 值的比较ture or false
但是,只有是同一个对象(内存地址)用==操作符时值才为true。
- 基本类型比较值:只要两个边路的值相等,即为true;
int a = 5; if (a==6) {...}
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true。
Person p1 = new Person();
Person p2 = new Person();
if (p1==p2) {...}
- 用 == 进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错;
public class ObjTest {
public static void main(String[] args) {
// equals()的测试
Person p1 = new Person();
Person p2 = new Person();
// 由于每一个都指向一个新对象,所以是false
System.out.println(p1 == p2); //false
System.out.println(p1.equals(p2)); //false
Person p3 = new Person();
Person p4 = p3; //指向同一个对象
// p5 = p3;// error
System.out.println(p3 == p4); //true
System.out.println(p3.equals(p4)); //true
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); //false,此时比较的是对象的地址
System.out.println(s1.equals(s2));//true,此时比较的是类型和内容
}
}
equals(): 所有类都继承了Object,也就获得了equals方法。还可以重写。
只能比较引用类型,其作用与==相同,比较是否指向同一个对象。
obj1.equals(obj2)
特别地,当用equals()方法进行比较时,对类File\String\Date及包装类(Wrapper Class)来说,是比较类型及内容,而不考虑引用的是否是同一个对象;因为这些类中重写了Object类的equals()方法。
String对象的创建
public class ObjTest {
public static void main(String[] args) {
char ch1 = 'A';
char ch2 = 12;
System.out.println(it == ch1); //true,65是A的ASCII码
System.out.println(12 == ch2); //true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2)); //true,特殊情况
// System.out.println("hello" == new java.sql.Date()); // 编译不通过,数据类型不一致
Person p1 = new Person();
p1.name = "ppp"; //字面量赋值
Person p2 = new Person();
p2.name = "ppp";
System.out.println(p1.name.equals(p2.name)); //true,name属性是字符串
System.out.println(p1.name == p2.name);//true
System.out.println(p1.name == "ppp");//true,实际上都是引用的字符串常量池里的同一个字符串
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); //false, ==比较的是内存地址,new方法是堆中生成两个String对象
System.out.println(s1.equals(s2)); //true
String s3 = "fff";
String s4 = "fff";
System.out.println(s3 == s4); //true
System.out.println(s3.equals(s4)); //true
}
}
练习
1、
package polym;
public class Order {
// 两个order对象的属性都相等,就是对象相等
// 当前的Order类的父类是Object类
int orderId;
String orderName;
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public String getorderName(){
return orderName;
}
public void setOrderName(){
this.orderName = orderName;
}
//两个参数的构造函数
public Order(int orderId, String OrderName){
this.orderId = orderId;
this.orderName = OrderName;
}
// 重写父类的equals方法
@Override
public boolean equals(Object obj) {
// return super.equals(obj);
boolean flag = false;
// obj是否是Order类的对象,Object是所有类的父类
//如下if判断,多态性应用举例
if(obj instanceof Order){
Order o = (Order) obj;
if (this.orderId == o.orderId && this.orderName.equals(orderName)){
flag = true;
}
}
return flag;
}
public static void main(String[] args) {
Order o1 = new Order(12,"dqwq");
Order o2 = new Order(12,"dqwq");
System.out.println(o1.equals(o2)); //true
}
}
2、构造方法目的是什么?
package polym;
public class MyDate {
// 构造方法,创建对象时就默认赋值
public MyDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
int year;
int month;
int day;
@Override
public boolean equals(Object obj) {
// return super.equals(obj);
int flag = 1;
/*
flag现在为0,如果要有一组值不相等就会+1,
如果全部都不相等flag就是3,2个不相等就是2
只有全部相等,flag=0
* */
if (obj instanceof MyDate){
MyDate md = (MyDate) obj;
flag = 0;
if (this.year != md.year){
flag += 1;
}
if (this.month != md.month){
flag += 1;
}
if (this.day != md.day){
flag += 1;
}
}
if (flag == 0){
return true;
}else {
return false;
}
}
public static void main(String[] args) {
MyDate m1 = new MyDate(2020,8,28);
MyDate m2 = new MyDate(2020,8,28);
System.out.println(m1.equals(m2)); // true
}
}
包装类和toString
将基本数据类型变成类——包装类
The constructors Integer(int), Double(double), Long(long) and so on are deprecated
针对八种基本定义相应的引用类型——包装类(封装类)
有了类的特点,就可以调用类中的方法。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
// jdk1.5后
public class Test {
public static void main(String[] args) {
int i = 500;
// Integer t = Integer.valueOf(i);
// System.out.println(t); // 500
// 自动装箱
Integer t1 = 12;
// 自动拆箱
int t2 = t1;
System.out.println(t1); // 12
System.out.println(t2); // 12
// 装箱
Boolean b2 = true;
// 拆箱
boolean b1 = b2;
System.out.println(b1); // true
System.out.println(b2); // true
// 字符串转换成基本数据类型
// 通过包装类的parseXxx(String s)静态方法;
Float f = Float.parseFloat("12.1");
System.out.println(f); // 12.1
// 基本数据类型转换成字符串
String s1 = String.valueOf(2.34f);
String intStr = 5 + "";
System.out.println(s1);//2.34
System.out.println(intStr); //5
// 基本数据的包装类
// 基本数据类型与字符串直接转化,这是主要应用!!!
int i = Integer.parseInt("123");
float f2 = Float.parseFloat("0.50");
boolean b = Boolean.parseBoolean("false");
String istr = String.valueOf(i);
String fstr = String.valueOf(f2);
String bstr = String.valueOf(true);
System.out.println(i); //123
System.out.println(f2); //0.5
System.out.println(b);//false
System.out.println(istr);//123
System.out.println(fstr);//0.5
System.out.println(bstr);//true
Integer a = 500;
String str1 = a.toString(); // str1 = “500”,a是类,有toString方法
String str2 = Integer.toString(314); //str2="314",将数字转成成字符串
String str3 = "4.56";
double ds = Double.parseDouble(str3); // 将字符串转换成数字
System.out.println(str1); //500
System.out.println(str2);//314
System.out.println(ds);//4.56
}
}
/*
拆箱:
*/
//基本数据类型int和sting的互换
String s = Integer.toString(314); //toString方法
int i = Integer.parseInt("314"); //parseInt方法
练习
父类Object的toString方法就输出当前对象的内存地址。如果要想输出类的其他信息,重写toString方法。(上述包装类就重写了toString方法)。
package polym;
import javax.swing.plaf.synth.SynthOptionPaneUI;
public class MyDate {
// 构造方法,创建对象时就默认赋值
public MyDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
int year;
int month;
int day;
@Override
public boolean equals(Object obj) {
// return super.equals(obj);
int flag = 1;
/*
flag现在为0,如果要有一组值不相等就会+1,
如果全部都不相等flag就是3,2个不相等就是2
只有全部相等,flag=0
* */
if (obj instanceof MyDate){
MyDate md = (MyDate) obj;
flag = 0;
if (this.year != md.year){
flag += 1;
}
if (this.month != md.month){
flag += 1;
}
if (this.day != md.day){
flag += 1;
}
}
if (flag == 0){
return true;
}else {
return false;
}
}
@Override
public String toString() {
// return super.toString();
String str = this.year + "-" + this.month + "-" + this.day;
return str;
}
public static void main(String[] args) {
MyDate m1 = new MyDate(2020,8,28);
MyDate m2 = new MyDate(2020,8,28);
System.out.println(m1.equals(m2)); // true
MyDate m = new MyDate(2020,12,12);
// System.out.println(m.toString()); //polym.MyDate@48140564
// System.out.println(m); //polym.MyDate@48140564
// 如果要改变上述,打印内存地址的情况,则重写方法。
// 现在要打印出年月日信息,重写toString()
// 打印m对象相当于执行打印 m.tostring(),这个在其他对象中也是如此。
System.out.println(m.toString()); //2020-12-12
System.out.println(m);//2020-12-12
}
}
关键字 static
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,
例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必再每一个中国人的实例对象中都单独分类一个用于代表国家名称的变量。
操作起来:
访问:
static是类变量。
类属性、类方法的设计思想
- 类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些类属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
- (有些方法不想因为对象的不同而频繁通过new对象方式去调用方法,那这个方法就写为static)
- 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的盗用。
- 类方法,也就是静态方法,常用做工具类。
练习
工具类:判断是否是字符串
public class Chinese {
public static void main(String[] args) {
String s = "";
System.out.println(Utils.isEmpty(s));//false
String s2 = "ll";
System.out.println(s2);//ll
}
}
public class Utils {
// 判断字符串是不是一个空字符串
public static boolean isEmpty(String s){
boolean flag = false;
if (s != null && !s.equals("")){
flag = true;
}
return false;
}
}
练习
public class Chinese {
static String country;
String name;
int age;
public static void test(){
System.out.println("这个是一个静态方法");
}
public static void main(String[] args) {
// 注意,static所修饰的,都不用创建new对象
Chinese.country = "China";
System.out.println(Chinese.country);//China
Chinese.test();//这个是一个静态方法
}
}
关键字 static
第3条:如country和test(),所有对象都用这两个。可以用来做计数count!
public class Chinese {
static String country;
// 新增,计数
public static int count;
String name;
int age;
// 新增,构造函数
public Chinese(){
Chinese.count += 1;
}
public static void test(){
System.out.println("这个是一个静态方法");
}
//新增
public static void showCount(){
System.out.println("总共new了 " + Chinese.count + " 个对象");
}
public static void main(String[] args) {
/*
// 注意,static所修饰的,都不用创建new对象
Chinese.country = "China";
System.out.println(Chinese.country);//China
Chinese.test();//这个是一个静态方法
*/
Chinese c1 = new Chinese();
Chinese c2 = new Chinese();
Chinese c3 = new Chinese();
Chinese c4 = new Chinese();
Chinese c5 = new Chinese();
Chinese.showCount(); // 总共new了 5 个对象
// 由此可以看出 Chinese.count 这个类属性被所有的实例化对象共享了
}
}
Chinese.count +=1,不该是this.count?
当我改为 this.count += 1;
Static member 'Chinese.count' accessed via instance reference
类方法通常用作工具类
- 没有对象的实例时,可以用类名.方法名()的形式访问由static标记的类方法。
- 在static方法内部只能访问类的static属性,不能方位类的非static属性。
public class Practice {
private int id;
private static int total = 0;
public static int getTotalPerson(){
// id++; // 非法,在static方法内部只能访问类的static属性,不能方位类的非static属性
return total;
}
public Practice(){
total++;
id = total;
}
public static void main(String[] args) {
//没有创建对象也可以访问静态方法
System.out.println(Practice.getTotalPerson()); // 0
Practice p1 = new Practice();
// System.out.println(Practice.getTotalPerson());
// System.out.println(p1.getTotalPerson()); // 1
System.out.println(Practice.getTotalPerson()); //1
}
}
回答上面的问题
this,super修饰的是实例化后的对象,可以访问对象的属性和方法。
他们俩并不修饰类。
单例设计模式
消耗问题如下,当new一个Single对象时:
使用单例模式解决什么问题?一般都是new对象太费劲,或者频繁地new新的对象没有必要。
private只有本类能使用。
调用:
这样的方法就不能使用了,因为被私有化了。Single.s也不能使用了,因为类变量也是private。
static修饰的变量在内存中只有一个副本,当且仅当在类初次加载时被初始化,意思就一次。
懒与饿汉的区别是对象什么时候被创建new的。