类与对象(下)
代码块
继承的定义与使用
重写(override)(重写重载异同)
super(super与this对比)
final
多态性
代码块
普通代码块
构造块
静态块
1.普通代码块
普通代码块就是在方法中直接使用{}定义的代码块。
普通代码块就是解决方法中需要重复定义同名变量的场景。
public class Test{
public static void main(String[] args){
{//普通代码块,在方法中直接使用{}定义
int x=10;
System.out.println(x);
}
int x=10;
System.out.println(x);
}
}
那么如果将代码稍作修改又会怎样呢?
public class Test{
public static void main(String[] args){
int x=10;
System.out.println(x);
{//将普通代码块放到此位置,是否可行?
int x=10;
System.out.println(x);
}
}
}
从以上两个代码我们可以看到,普通代码块中定义的变量为局部变量,其作用域为整个普通代码块中,故第一个代码可行;第二个代码中第一个定义的变量x为全局变量,其作用域为整个主方法,故此时为重复定义。总而言之,注意变量作用域即可。
2.构造块(生成对象时调用)
构造块是指在类中使用{}定义的代码块(不加修饰符)。
构造块主要作用是完成类中普通属性初始化操作(因无法传参,故所有变量初始化结果相同)。
class Person{
public Person(){
System.out.println("1.这是Person类的构造方法");
}
{
System.out.println("2.这是Person类的构造块");
}
}
public class Test{
public static void main(String[] args){
Person per=new Person();
}
}
由以上代码可以看到,构造块优于构造方法执行。且构造块与构造方法均是每生成一个对象就调用一次(构造块与构造方法位置可任意前后)。
3.静态块
静态代码块:使用static修饰并定义在类中的代码块。
在非主类中
在主类中
(1)在非主类中
class Person{
public Person(){
System.out.println("1.这是Person类的构造方法");
}
{
System.out.println("2.这是Person类的构造块");
}
static{
System.out.println("3.这是Person类的静态块");
}
}
public class Test{
public static void main(String[] args){
System.out.println("4.-------start-------");
new Person();
new Person();
System.out.println("5.--------end--------");
}
}
由以上代码我们可以看出静态块优先于构造块执行,构造块优先于构造方法执行,且无论新生成几个对象,静态块只执行一次(因静态家族与对象无关)。
静态块是在加载类时执行一次,何为加载类呢?
加载类就是程序在启动时,一定要用到主类(因主类是程序的入口),故称为加载主类。其他类若调用就加载不调用就不加载。以上代码中静态块位于Person类中,故先在加载主类时先执行主方法,再加载Person类时执行静态块、构造块、构造方法,最后再退出主方法。
执行分析图:
注意:
1.静态块的作用主要是为static属性初始化。
2.类中static属性有默认值。
(2)在主类中
public class Test{
public Test(){
System.out.println("1.这是Test类的构造方法");
}
{
System.out.println("2.这是Test类的构造块");
}
static{
System.out.println("3.这是Test类的静态块");
}
public static void main(String[] args){
System.out.println("4.-------start-------");
new Test();
new Test();
System.out.println("5.--------end--------");
}
}
由以上代码我们可以看出,当静态块位于主方法中时,静态块优先于主方法执行,原因是在执行主方法前会先加载主类,故先执行静态块;我们还可以看到主方法优先于构造块执行,因构造块需要对象调用,故应先执行主方法生成对象,若不生成对象则不执行构造块。
public class Test{
public Test(){
System.out.println("1.这是Test类的构造方法");
}
{
System.out.println("2.这是Test类的构造块");
}
static{
System.out.println("3.这是Test类的静态块");
}
public static void main(String[] args){
System.out.println("4.-------start-------");
// new Test();//不生成对象时不执行构造块、构造方法
// new Test();
System.out.println("5.--------end--------");
}
}
程序执行分析图:
继承的定义与使用
定义:指无需重复编写代码的情况下拥有父类的所有成员。
class 子类 extends 父类{
}
继承的主要作用在于在已有基础上继续进行功能的扩充及代码的重用。
要使用继承,必须满足“is-a”原则。(例如student is a person)(即子类必须包含于父类)
在继承时子类会继承父类的所有结构,所有能直接调用的(public定义的方法或属性)称为显式继承;
不能直接调用,需要通过调用setter、getter语句间接访问的称为隐式继承。
class Person{//父类
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 this.name;
}
public int getAge(){
return this.age;
}
public void getPersonInfo(){
System.out.println(this.name+"今年"+this.age);
}
}
class Student extends Person{
//子类继承父类,父类公共属性(public)定义的方法或属性在子类中可直接使用,称为显式继承;父类私有属性(private)定义的方法或属性子类不能直接访问,需利用访问setter、getter方法间接访问
private String school;//子类独有的类属性
public void setSchool(String school){
this.school=school;
}
public String getSchool(){
return this.school;
}
public void getStudentInfo(){
System.out.println(school);
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student();
stu.setName("张三");//通过访问父类setName方法间接改变私有属性name的值
stu.setAge(18);//同理name
stu.setSchool("清华大学");
stu.getPersonInfo();
stu.getStudentInfo();
}
}
通过以上代码我们可以得知,当发生继承操作时,子类无需书写程序就可直接继承父类的操作,实现了代码的复用性。子类最低也可以维持与父类相同的功能,子类也可以进行功能的扩充。
继承的限制
(1)子类在进行实例化之前一定会首先实例化父类对象。默认调用父类的构造方法后再调用子类的构造方法进行子类对象初始化。
class Person{
public Person(){
System.out.println("1.这是父类的构造方法!");
}
}
class Student extends Person{
public Student(){
System.out.println("2.这是子类的构造方法!");
}
}
public class Test{
public static void main(String[] args){
new Student();
}
}
以上代码中我们并没有直接调用父类任何方法,但却是先初始化父类对象再初始化子类对象。
但一定要注意的是继承中一个子类只能继承一个父类,但一个父类能被多个子类继承。
假设两个子类要同时继承一个父类,可多层继承,但一般建议最多多层继承3次。
class Person{
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return this.name;
}
}
class Worker extends Person{//Worker继承Person类
private int age;
public void setAge(int age){
this.age=age;
}
public int getAge(){
return this.age;
}
}
class Student extends Worker{//Student类继承Worker类,相当于间接继承了Person类
private String school;
public void setSchool(String school){
this.school=school;
}
public String getSchool(){
return this.school;
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student();
stu.setName("张三");
stu.setAge(18);
stu.setSchool("清华大学");
System.out.println(stu.getName()+"今年"+stu.getAge()+"在"+stu.getSchool()+"读书");
}
}
方法重写(覆写)(override)
因在学习了继承之后,就有可能出现这样一种情况,当子类与父类定义了相同的方法或属性时(方法重写发生在有继承关系的类之间),这样的操作就称为重写(覆写)。
方法重写定义:子类定义了与父类方法名称、参数个数、类型及返回值全部相同的方法。但是被覆写不能拥有比父类更严格的访问权限(>=父类的访问权限)。
什么是更为严格的访问权限呢?
目前为止,我们所了解到的访问权限有public、default、private三种,其访问大小排名为private<default(访问包的权限(默认访问权限),意思是在同一个源文件中可使用)<public,当父类方法访问权限为public时,子类必须是public,若子类为default时,子类可为default或public。
那么思考一个问题,当父类方法访问权限为private时,子类访问权限能用public吗?
class Person{
public void fun(){
this.print();
}
private void print(){//表示只能父类使用,子类无法使用
System.out.println("1.这是父类!");
}
}
class Student extends Person{
public void print(){//此时不是方法覆写,print()只是子类中的方法
System.out.println("2.这是子类!");
}
}
public class Test{
public static void main(String[] args){
new Student().fun();
}
}
以上结果显示,方法并没有覆写,原因是因为一旦方法用private定义,则子类就根本不知道父类存在此类,故子类继承父类所有结构后并没有覆写成功,故输出的是1。
故我们可得出结论是方法覆写不可出现private定义,若出现,则不是方法重写。
//正确的方法重写
class Person{
public void print(){
System.out.println("1.这是父类!");
}
}
class Student extends Person{
//方法重写,方法名称、参数个数类型、返回值均相同,只有访问权限不同(子类>=父类)
public void print(){
System.out.println("2.这是子类!");
}
}
public class Test{
public static void main(String[] args){
new Student().print();
}
}
存在方法覆写时,如何判断调用哪一个类方法?
1.你当前使用的对象是通过哪个类new的;(上文代码new Student())
2.当调用某个方法时,如果该方法已被覆写,一定输出覆写后的方法。
注意:属性覆写没什么实际意义(因类的属性都要求private封装,一旦封装,子类就不知道父类有什么属性),故不存在属性覆写的问题了。
结论:
1.子类覆写父类方法是因为父类方法功能不足时才需要覆写。
2.方法覆写使用的是public权限。
方法重载(overload)和方法重写(override)的区别:
super关键字(用于有继承存在的程序中)
super关键字主要是调用父类的构造方法。
super调用构造方法
super调用普通方法
super调用普通属性
1.super调用构造方法
(1)若父类存在无参构造则在子类的构造方法中写不写super()均可(因此时系统会默认添加,表示先调用父类的无参构造),此时若省略super()语句,子类中可使用this调用构造方法(此时super()语句优先于this语句)。
class Person{
public Person(){
System.out.println("1.这是父类的无参构造方法");
}
}
class Student extends Person{
private String name;
public Student(){
System.out.println("2.这是子类的无参构造方法");
}
public Student(String name){
this();//调用无参构造
this.name=name;
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student("张三");
}
}
(2)若父类不存在无参构造而是定义了其他有参构造时,必须添加super(参数)指明调用父类哪个有参构造;此种情况子类不存在this调用构造方法(原因是super()/this均需放在构造方法首行)。
class Person{
private String name;
public Person(String name){
System.out.println("1.这是父类的有参构造方法");
}
}
class Student extends Person{
private int age;
public Student(){
System.out.println("2.这是子类的无参构造方法");
}
public Student(int age){
super(name);
this();
this.age=age;
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student(18);
}
}
2.super调用普通方法
super.方法名(参数)表示直接从父类中找到同名方法并进行调用。
class Person{
public void print(){
System.out.println("1.这是父类");
}
}
class Student extends Person{
public void print(){
super.print();//调用父类普通方法
System.out.println("2.这是子类");
}
}
public class Test{
public static void main(String[] args){
new Student().print();
}
}
3.super调用普通属性
super.属性名表示直接从父类中找到同名属性并进行调用。
class Person{
public String name="张三";
}
class Student extends Person{
public String name="小花";
public void print(){
System.out.println(super.name);//调用父类普通属性
System.out.println(this.name);//调用自身普通属性
}
}
public class Test{
public static void main(String[] args){
new Student().print();
}
}
super与this的区别
final关键字(封装)
final被称为终结器。
final修饰类
final修饰方法
final修饰属性
final修饰引用类型
数据类型转换
1.final修饰类
此类不能有子类(无法使用extends继承该类),若一个类被final修饰,其内部所有成员方法默认添加final关键字,均不可覆写(因无子类)。
eg:JDK中String类就使用final修饰(各平台使用的Stirng类均相同)。
final class Person{//此时Person类为最终类,不可被继承
public void print(){
System.out.println("1.这是父类");
}
}
class Student extends Person{
public void print(){
super.print();
System.out.println("2.这是子类");
}
}
public class Test{
public static void main(String[] args){
new Student().print();
}
}
2.final修饰方法
final修饰的方法不能被子类覆写。
class Person{
public final void print(){
System.out.println("1.这是父类");
}
}
class Student extends Person{
public void print(){
super.print();
System.out.println("2.这是子类");
}
}
public class Test{
public static void main(String[] args){
new Student().print();
}
}
3.final修饰成员变量
final修饰的成员变量就变成了常量,必须在声明时赋值,并且不能被修改。
public final int a=100;
定义常量(public static final),常量全用大写字母,多个单词间以_间隔。
public static final int MAX=120;
用static与final结合定义常量的原因是,若只用final定义变量,只是让变量的值不可变,产生多个变量时值均相同,但还是放在堆中的每个对象中,会浪费空间;若将其再用static进行修饰,相当于将其拿出来放置与全局数据区。
4.final修饰引用数据类型
class Person{
private String name;
private int age;
public final void setName(String name){
this.name=name;
}
public final void setAge(int age){
this.age=age;
}
public void setNameAgain(String name){//修改堆中name的值
this.name=name;
}
public void getPersonInfo(){
System.out.println(this.name+"今年"+this.age);
}
}
public class Test{
public static void main(String[] args){
Person per=new Person();
per.setName("张三");
per.setAge(18);
per.setNameAgain("小花");
per.getPersonInfo();
}
}
由以上代码我们可以得知final在修饰引用数据类型时,只是栈中地址不可修改,堆中数值可修改;final修饰其他类型时数值不可变。
5.数据类型转化(重要)
当使用+、-、*、/、%运算操作时,只要两个操作数中任意一个为long型、float型、double型,另一个操作数将会被自动转化为long型、float型、double型,并且结果也为long型、float型、double型;若一个为float型,另一个为double型,则自动提升为double型(以大为主);否则,当操作数为byte、int、short、char型,两个数都会被转化为int型,并且结果也为int型。但final修饰的域类型不会发生变化,值也不可修改。
看一段错误的代码:
byte b1=1,b2=2,b3,b6,b8;
final byte b4=4,b5=6,b7=9;
public void test(){
b3=(b1+b2);//错误,b1+b2转化为int型,b3为byte型,放不下,若修改应要强转b3=(byte)(b1+b2)
b6=(b4+b5);//正确,b4、b5均用final定义,类型不变
b8=(b1+b4);//错误,b1转化为int型,b4为byte型,结果为int型,修改为b8=(byte)(b1+b4)
b7=(b2+b5);//b7用final定义,值不可改变
}
多态性(本质为方法覆写)
方法的多态性:
(1)方法的重载:同一个方法名称可以根据参数的类型或个数不同调用不同的方法体。
(2)方法的覆写:同一个父类的方法,可以根据实例化子类的不同也有不同的实现。
对象的多态性:
(1)对象的向上转型:父类 父类对象=子类实例。(自动 99%)
(2)对象的向下转型:子类 子类对象=(子类)父类实例。(强制 1%)
对象的向上转型(目的是为了参数统一化)
class Person{
public void print(){
System.out.println("1.这是父类!");
}
}
class Student extends Person{
public void print(){
System.out.println("2.这是子类!");
}
}
public class Test{
public static void main(String[] args){
Person per=new Student();//对象向上转型
per.print();
}
}
无论是否发生了转型,核心本质还在于使用的是哪个类(new在哪里),调用的方法若被覆写就是覆写后的方法。
对象的向下转型(使用场景:父类需要子类扩充操作时采用向下转型)
向下转型指的是将父类对象转化为子类对象,但在向下转型前必须得先向上转型。且向下转型存在安全问题。
class Person{
public void print(){
System.out.println("1.这是父类!");
}
}
class Student extends Person{
public void print(){
System.out.println("2.这是子类!");
}
public void fun(){
System.out.println("3.这是子类特有的!");
}
}
public class Test{
public static void main(String[] args){
Person per=new Student();//首先向上转型
per.print();
Student stu=(Student)per;//父类若想获得子类特有的,就得向下转型
stu.fun();
}
}
那么这样的转化到底有什么作用呢?
class Person{
public void print(){
System.out.println("1.这是Person类!");
}
}
class Student extends Person{
public void print(){
System.out.println("2.这是Student类!");
}
}
class Worker extends Person{
public void print(){
System.out.println("3.这是Worker类!");
}
}
public class Test{
public static void main(String[] args){
Who(new Student());//自动向上转型
Who(new Worker());
}
public static void Who(Person per){//输出为覆写后的内容
per.print();
}
}
总结:
1.对象多态性的核心是方法的覆写。
2.通过对象向上转型可以实现接受参数的统一,向下转型可以实现子类扩充方法的调用(一般不使用,有安全隐患)。
3.两个没有关系的类对象不能进行转型(有继承关系)。