this 关键字(一)
this关键字
之前说过,变量名称或方法参数名称,要见名知意,下列两个set方法的参数名,就显得太LOW了。
public class Student { private String name; private int age; public void setName(String n) { name = name; } public void setAge(int a) { age = age; } public String getName() { return name; } public int getAge() { return age; } }
不就是设置名字和年龄吗,如果此时把参数名分别改为name和age。
public class Student { private String name; private int age; public void setName(String name) { name = name; } public void setAge(int age) { age = age; } public String getName() { return name; } public int getAge() { return age; } }
此时会发现参数根本就设置不进去,name和age打印出来都是各自的初始值,运行测试类的结果如下:
null,0
导致参数设置不进去的原因是:
局部变量和成员变量同名,此时在方法中调用变量时根据就近原则,优先使用局部变量,示意图如
下。
编辑
可以看出setName方法中两次使用的name,都是直接寻找距离自己最近的形参name,就相当于把参数name的值设置给参数name,根本就没有把参数值设置给成员变量。
注意:
当在一个作用域访问变量时,首先在当前作用域查找该变量,
如果能找到,不继续查找。
如果在当前作用域找不到该变量,尝试去上一层作用域查找,如果找到,停止查找,如果找不 到,继续上一层查找,依次类推。这整个过程形成一个查找链,这个称为作用域链。
该问题,更专业的叫法是局部变量和成员变量存在二义性,也就是变量名有歧义。
为了解决该问题——有请this关键字。
使用 this.变量名 的语法,此时访问的就是成员变量,this的其他操作,后面再讲解。
编辑
具体代码如下:
public class Student { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getAge() { return age; } }
使用构造器还是setter方法
构造器和setter方法都可以给对象设置数据:
构造器,在创建对象的时候设置初始数据,只能初始化一次。
setter方法,创建对象后再设置初始数据,可以设置多次。
继承思想
(1)使用面向对象的知识定义出老师(Teacher)、学生(Student)、员工(Employee)三个类:
老师:拥有名字、年龄、级别三个状态,有授课和休息两个功能
学生:拥有名字、年龄、学号三个状态,有学习和休息两个功能
员工:拥有名字、年龄、入职时间三个状态,有工作和休息两个功能
代码截图如下:
编辑
此时,发现三个类中的存在着大量的共同代码,而我们要考虑的就是如何解决代码重复的问题。 面向对象的继承思想,可以解决多个类存在共同代码的问题。
继承关系设计图:
编辑
被继承的类,称之为父类、基类;
继承父类的类,称之为子类,拓展类;
父类:存放多个子类共同的字段和方法;
子类:存放自己特有的字段和方法。
继承语法
在java程序中,如果一个类需要继承另一个类,此时使用extends关键字。
public class 子类名 extends 父类名{ }
注意:
Java中类只支持单继承,但是支持多重继承。也就是说一个子类只能有一个直接的父类,父类也可以再有父类。
下面是错误的写法! Java中的类只支持单继承。
class SuperClass1{} class SuperClass2{} class SubClass extends SuperClass1,SuperClass2{} //错误
下面代码是正确的。一个父类可以有多个子类。
class SuperClass{} class SubClass1 extends SuperClass{} class SubClass2 extends SuperClass{}
下面代码是正确的,支持多重继承。
class SuperSuperClass{} class SuperClass extends SuperSuperClass{} class SubClass extends SuperClass
Object类是Java语言的根类,任何类都是Object的子类,要么是直接子类,要么是间接子类;
public class Person{} 等价于 public class Person extends Object{}
继承操作
父类代码:
public class Person { private String name; private int age; public void rest() { System.out.println("休息"); } 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 class Student extends Person{ private String sn;// 学号 public void study() { System.out.println("学习"); } public String getSn() { return sn; } public void setSn(String sn) { this.sn = sn; } }
测试类:
public class ExtendsDemo { public static void main(String[] args) { //创建学生对象 Student stu = new Student(); //设置字段信息 stu.setName("will"); //继承了父类 stu.setAge(17); //继承了父类 stu.setSn("s_123"); //调用方法 stu.study(); stu.rest();//继承了父类 } }
子类可以继承到父类哪些成员
子类继承父类之后,可以拥有到父类的某一些成员(字段和方法),根据访问修饰符来判断:
如果父类中的成员使用public和protected修饰,子类都能继承;
如果父类和子类在同一个包中,使用缺省访问修饰的成员,此时子类可以继承到 ;
如果父类中的成员使用private修饰,子类继承不到。private只能在本类中访问;
父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同。
关键字 | 本类 | 同包子类 | 同包其他类 | 不同包子类 | 不同包其他类 |
private | ✔ | ✘ | ✘ | ✘ | ✘ |
默认 | ✔ | ✔ | ✔ | ✘ | ✘ |
protected | ✔ | ✔ | ✔ | ✔ | ✘ |
public | ✔ | ✔ | ✔ | ✔ | ✔ |
private :私有的,本类可见。
默认 ( friendly ) : 默认的,同包可见。
protected : 受保护的。①同包可见 ②子类可见
public : 公共的,任意地方都可见。
访问权限 : private < 默认 < protected < public
方法覆盖
子类继承了父类,可以拥有父类的部分方法和成员变量。可是当父类的某个方法不适合子类本身的特征时,此时怎么办?比如鸵鸟(Ostrich)是鸟类(Bird)中的一个特殊品种,所以鸵鸟类是鸟类的一个子类,但是鸟类有飞翔的功能,但是对应鸵鸟,飞翔的行为显然不适合于它。
父类:
public class Bird { public void fly() { System.out.println("飞呀飞..."); } }
子类:
public class Ostrich extends Bird{ }
测试类:
public class OverrideDemo { public static void main(String[] args) { //创建鸵鸟对象 Ostrich os = new Ostrich(); //调用飞翔功能 os.fly(); } }
控制台输出:
飞呀飞...
上述代码从语法是正确的,但从逻辑上是不合理的,因为鸵鸟不能飞翔,此时怎么办?——方法覆盖操作。
方法覆盖操作
当子类存在一个和父类一模一样的方法时,我们就称之为子类覆盖了父类的方法,也称之为重写。 那么我们就可以在子类方法体中,重写编写逻辑代码。
public class Ostrich extends Bird{ public void fly() { System.out.println("扑扑翅膀,快速奔跑..."); } }
控制台输出:
扑扑翅膀,快速奔跑...
方法的调用顺序
通过对象调用方法时,先在子类中查找有没有对应的方法,若存在就执行子类的,若子类不存在就 执行父类的,如果父类也没有,报错。
方法覆盖的细节
private修饰的方法不能被子类所继承,也就不存在覆盖的概念。
(1)实例方法签名必须相同 (方法签名= 方法名 + 方法的参数列表);
(2)子类方法的返回值类型是和父类方法的返回类型相同或者是其子类;
(3)子类方法的访问权限比父类方法访问权限更大或相等;
如果父类方法是private,子类方法不能重写。==> 重写建立在继承的基础上,没有继承,就不能重写。
(4)子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型。
super 关键字(一)
问题,在子类中的某一个方法中需要去调用父类中被覆盖的方法,此时得使用super关键字。
public class Ostrich extends Bird{ public void fly() { System.out.println("扑扑翅膀,快速奔跑..."); } public void say() { super.fly();//调用父类被覆盖的方法fly();//调用本类中的方法 } }
如果调用被覆盖的方法不使用super关键字,此时调用的是本类中的方法。
super关键字表示父类对象的意思,更多的操作,后面再讲解。
super.fly()可以翻译成调用父类对象的fly方法。
抽象方法和抽象类
(1)求圆(Circle)和矩形(Rectangle)两种图形的面积。
分析:无论是圆形还是矩形,还是其他形状的图形,只要是图形,都有面积,也就说图形都有求面 积的功能,那么我们就可以把定义一个图形(Graph)的父类,该类拥有求面积的方法,但是作为图形 的概念,而并不是某种具体的图形,那么怎么求面积是不清楚的,姑且先让求面积的getArea方法返回 0。
父类代码:
public class Graph { public double getArea() { return 0.0; } }
子类代码(圆形):
public class Circle extends Graph { private int r; //半径 public void setR(int r) { this.r = r; } public double getArea() { return 3.14 * r * r; } }
子类代码(矩形):
public class Rectangle extends Graph { private int width; // 宽度 private int height; // 高度 public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public double getArea() { return width * height; } }
测试类:
public class GraphDemo { public static void main(String[] args) { // 圆 Circle c = new Circle(); c.setR(10); double ret1 = c.getArea(); System.out.println("圆的面积:" + ret1); // 矩形 Rectangle r = new Rectangle(); r.setWidth(5); r.setHeight(4); double ret2= r.getArea(); System.out.println("矩形的面积:" + ret2); } }
控制台输出:
圆的面积:314.0 矩形的面积:20.0
引出抽象方法
问题1:既然不同的图形求面积的算法是不同的,所以必须要求每一个图形子类去覆盖getArea方法,如果没有覆盖,应该以语法报错的形式做提示。
问题2:在Graph类中的getArea方法的方法体没有任何存在意义,因为不同图形求面积算法不一样,子类必须要覆盖getArea方法。
要满足上述对方法的要求,就得使用abstract来修饰方法,被abstract修饰的方法具备两个特征:
该方法没有方法体
要求子类必须覆盖该方法
这种方法,我们就称之为抽象方法。
抽象方法和抽象类
使用abstract修饰的方法,称为抽象方法。
public abstract 返回类型 方法名(参数);
抽象方法的特点:
使用abstract修饰,没有方法体,留给子类去覆盖
抽象方法必须定义在抽象类或接口中
使用abstract修饰的类,成为抽象类。
public abstract class 类名{ }
一般的,抽象类以Abstract作为类名前缀,如AbstractGraph,一看就能看出是抽象类。
抽象类的特点:
抽象类不能创建对象,调用没有方法体的抽象方法没有任何意义
抽象类中可以同时拥有抽象方法和普通方法
抽象类要有子类才有意义,子类必须覆盖父类的抽象方法,除非子类也是抽象类。
父类代码:
public abstract class AbstractGraph { public abstract double getArea(); //没有方法体 }
子类代码:
public class Circle extends AbstractGraph { private int r;// 半径 public void setR(int r) { this.r = r; } public double getArea() { //覆盖父类抽象方法 return 3.14 * r * r; //编写方法体 } }
测试类没有改变。
Object类和常用方法
Object本身表示对象类的意思,是Java中的根类,要么是一个类的直接父类,要么就是一个类的间接父类。
class A{} 其实等价于 class A extends Object{}
因为所有类都是Object类的子类, 所有类的对象都可以调用Object类中的方法,常见的方法:
boolean equals(Object obj):拿当前调用该方法的对象和参数obj做比较
在Object类中的equals方法和“ == ”符号相同都是比较对象是否是同一个的存储地址。
public class ObjectDemo { public static void main(String[] args) { //创建Person对象p1 Person p1 = new Person(); //创建Person对象p2 Person p2 = new Person(); //比较p1和p2的内存地址是否相同 boolean ret1 = p1 == p2; boolean ret2 = p1.equals(p2); System.out.println(ret1); //false System.out.println(ret2); //false } }
官方建议:每个类都应该覆盖equals方法去比较我们关心的数据,而不是内存地址。
String toString():表示把对象中的字段信息转换为字符串格式
打印对象时其实打印的就是对象的方法
Person p = new Person(); p.setName("will"); p.setAge(17); System.out.println(p); System.out.println(p.toString());
其中:
System.out.println(p); 等价于 System.out.println(p.toString());
打印格式如:
ink.banq.demo.Person@15db9742
默认情况下打印的是对象的值,但是我们更关心对象中字段存储的数据。
官方建议:应该每个类都应该覆盖返回我们关心的数据,如:
public class Person { private String name; private int 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 String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
此时打印对象,看到的是该对象的字段信息。
Person [name=will, age=17]
== 符号到底比较的是什么
比较基本数据类型:比较两个值是否相等;
比较对象数据类型:比较两个对象是否是同一块内存空间;
每一次使用new关键字,都表示在堆中创建一块新的内存空间。
以上就是Java入门第八天的全部内容了。
资料文档地址:Java开发零基础篇:day08面向对象(二).pdf