一、成员变量初始化
1.成员变量分为两大类:
(1)静态变量:有static修饰
(2)实例变量:没有static修饰
2.静态变量的初始化
(1)在类初始化进行时,一个类只有一次。因为静态变量是所有对象共享的,属于类的,不是专属与某个对象
(2)和静态变量的显示复制语句和静态代码块有关
本质上把两部分代码合并到一个()类初始化方法中,由类加载器在类初始化时调用。
3.实例变量的初始化
1.一定是在new对象时进行的,而且每次new对象都要初始化一次,因为每一个对象都是独立的
A:super()或super(实参列表)
B:实例变量的显示赋值
C:非静态代码块
D:构造器
以上4个部分:A必须先完成,然后B和C的话是按照编写的顺序进行,最后在执行D构造器中除了A的剩下的代码。
本质上,创建对象时,是在执行实例初始化方法,一个类可能有多个实例初始化方法,有几个看你编写的构造器的个数,如果没有编写,那么只有一个。
实例初始化方法的代码就是由以上四个部分的代码组成。
A:super()或super(实参列表) 在实例初始化方法的首行
B:实例变量的显示赋值
C:非静态代码块
D:构造器中剩下的代码
要注意的是:B 和 C 是按照编写的顺序组装,可能先B后C,也可能先C后B
注意:super()现在不仅仅是代表父类的无参构造了,而是代表父类的()无参实例初始化方法。
super(实参列表)现在不仅仅是代表父类的有参构造了,而是组装后的父类的(…)有参实例初始化方法。
Demo类编译后:
class Demo {
private int a;
<init>(){//无参实例初始化方法
a= getNum();
System.out.println("非静态代码块,a = " + a);
System.out.println("无参构造");
}
<init>(int a){//有参实例初始化方法
a= getNum();
System.out.println("非静态代码块,a = " + a);
this.a = a;
System.out.println("有参构造a = " + this.a);
}
public int getNum(){
System.out.println("getNum()被执行了,a = " + a);
return 10;
}
}
*/
public class TestInitialize {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println("----------------------------");
Demo d2 = new Demo(20);
}
}
class Demo {
private int a = getNum();
{
System.out.println("非静态代码块,a = " + a);
}
Demo(){
System.out.println("无参构造");
}
Demo(int a){
this.a = a;
System.out.println("有参构造a = " + this.a);
}
public int getNum(){
System.out.println("getNum()被执行了,a = " + a);
return 10;
}
}
/*
4.静态变量的初始化和非静态的实例变量的初始化合起来。
(1)静态的变量的初始化先进行
(2)完成之后,才会轮到实例变量的初始化
如果有父类,有子类。
先父类的静态,再子类的静态。然后父类的非静态,最后子类的非静态。
每一个类只会初始化一次。即每个类的静态初始化只会完成一次。
public class TestInitialize3 {
public static void main(String[] args) {
Father f = new Father();//32314
Son son = new Son();//单独它 3276 314 758
//两句合起来 32314 76 314 758 父类的静态不需要初始化2次
Son son2= new Son();
//三句合起来 32314 76 314 758 314 758 子类的静态也不用执行2次
}
}
class Father{
private static int a = getNum();
private int b = getNum();
{
System.out.println("1 Father:not_static");
}
static{
System.out.println("2 Father:static");
}
public static int getNum(){
System.out.println("3 getNum()");
return 1;
}
Father(){
System.out.println("4 Father()无参构造");
}
}
class Son extends Father{
private static int a = getNum();
private int b = getNum();
{
System.out.println("5 Son:not_static");
}
static{
System.out.println("6 Son:static");
}
public static int getNum(){
System.out.println("7 getNum()");
return 1;
}
Son(){
System.out.println("8 Son()无参构造");
}
}
二、多态
1.三大基本特征的好处:
1封装:(1)安全(2)便捷
2.继承:(1)复用(2)扩展)(3)is-a关系
3.多态:代码的灵活性
2.多态的作用:代码的灵活性
3.多态要求:
1.有继承关系
2.有方法重写
3.有父类的引用指向子类的实例
3.语法格式
父类 变量 = new 子类(【实参列表】);
本质上: 本质上:父类的引用指向了子类的对象,就是多态引用。
编译时类型:看父类
编译时,只能调用父类声明的方法,无法调用子类“扩展”的方法。
运行时类型:看子类
运行时:
如果子类重写了父类的方法,运行时执行的一定是子类重写的代码。
如果子类没有重写父类的方法,运行时执行的还是父类的方法。
public class TestPolymorphism {
public static void main(String[] args) {
Animal animal = new Animal();//本态引用
Dog dog = new Dog();//本态引用
Animal animal1 = new Dog();//多态引用*/
Animal animal = new Animal();
animal.eat();//吃东西
Dog dog = new Dog();
dog.eat();//啃骨头*/
Animal animal = new Dog();
animal.eat();//啃骨头
// animal.watchHouse();//编译报错
}
}
class Animal{
public void eat(){
System.out.println("吃东西");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("啃骨头");
}
public void watchHouse(){
System.out.println("看家");
}
}
4.多态的应用之一:多态数组
数组的元素类型是父类的类型,数组中存储的元素对象是子类的对象。
我们可以使用父类类型的数组统一管理它各种子类的对象。
举例:
声明一个图形类Graphic,包含一个方法:public double area(),返回0.0
声明它的两个子类,圆Circle和矩形Rectangle,重写area()
用一个数组存储多个的图形对象(可能有圆,可能有矩形),并且要遍历他们的面积。
public class TestUse1 {
public static void main(String[] args) {
Circle c1 = new Circle(2.5);
Circle c2 = new Circle(4);
Rectangle r1 = new Rectangle(2,6);
Rectangle r2 = new Rectangle(1,3);
Circle[] arr1 = new Circle[4];//不行,只能装Circle对象
arr1[0] = c1;
arr1[1] = c2;
// arr1[2] = r1;//矩形的对象是不能赋值给Circle类型的元素
// arr1[3] = r2;
Graphic[] arr = new Graphic[4];
arr[0] = c1; //左边的arr[0]的类型是Graphic,右边的c1是Circle对象
//隐含了 Graphic arr[0] = new Circle(2.5);
arr[1] = c2;
arr[2] = r1;
arr[3] = r2;
for(int i=0; i<arr.length; i++){
System.out.println(arr[i].area());
/*
编译期间,arr[i]是Graphic类,看能不能通过编译,是看Graphic中是否有一个无参的area()方法。
运行期间:arr[i]的运行时类型是什么,就执行谁的area()方法
arr[0]是Circle,运行的是Circle里面重写的area();
arr[2]是Rectangle,运行的是Rectangle里面重写的area();
*/
}
//按照对象的面积排序
for(int i=1; i<arr.length; i++){
for(int j=0; j<arr.length-i; j++){
if(arr[j].area() > arr[j+1].area()){
//交换arr[j]与arr[j+1]
Graphic temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("排序后:");
for(int i=0; i<arr.length; i++) {
System.out.println(arr[i].area());
}
}
}
class Graphic {
public double area() {
return 0.0;
}
}
class Circle extends Graphic {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Graphic{
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
5.多态的应用之二:多态参数,多态形参和实参
形参是父类的类型
实参是子类的对象
public class TestUse2 {
public static void main(String[] args) {
meet(new Chinese());//实参给形参赋值 形参 Person person = new Chinese();实参
meet(new American());
meet(new Thai());
}
//重载
/*public static void meet(Chinese chinese){
chinese.welcome();
}
public static void meet(American american){
american.welcome();
}
public static void meet(Thai thai){
thai.welcome();
}*/
public static void meet(Person person){
person.welcome();
}
}
class Person{
public void welcome(){
System.out.println("微笑");
}
}
class Chinese extends Person{
@Override
public void welcome() {
System.out.println("你好");
}
}
class American extends Person{
@Override
public void welcome() {
System.out.println("hello");
}
}
class Thai extends Person{
@Override
public void welcome() {
System.out.println("sawadika");
}
}
6.多态的应用之三:多态返回值
方法的返回值类型是父类的类型,
实际返回的对象是子类的对象。
public class TestUser3 {
public static void main(String[] args) {
Car car = buy("宝马");//左边是Car car,右边返回的对象是new BMW()
car.drive();
}
public static Car buy(String info){
if("宝马".equals(info)){
return new BMW();
}else if("奔驰".equals(info)){
return new Benz();
}else{
return new Car();
}
}
}
class Car{
public void drive(){
System.out.println("~~~~");
}
}
class BMW extends Car{
@Override
public void drive() {
System.out.println("宁可坐在宝马车上哭,也不坐在自行车上笑,宝马车嘟嘟嘟~~~");
}
}
class Benz extends Car{
@Override
public void drive() {
System.out.println("坐在引擎盖上哭,奔驰车滴滴滴~~~");
}
}
7.类型转换
引用数据类型,而且要求是父子类关系,包括爷爷和孙子类。
(1)向上转型:up-casting
当我们把子类的对象/变量 赋值给父类的变量时,就会“自动”发生向上转型。
(2)向下转型:down-casting
当我们把父类的变量 赋值给子类的变量时,就需要“强制”类型转换,称为向下转型。
注意:无论是向上还是向下都只针对“编译时”类型。而“运行时”类型从头到尾都不会改变。
影响:
编译时:向上转型之后,只能看到父类声明的成员,无法访问子类扩展的成员(不管是成员变量还是成员变量)
向上和向下转型,编译和运行能否通过:
(1)编译通过:只要有父子类关系即可,符合语法格式
(2)运行通过:
向上转型:= 右边的对象的运行时类型 小于等于 =左边的变量的类型即可
向下转型:= 右边的变量中的对象的运行时类型 小于等于 = 左边的变量的类型即可
举例:
Animal a1 = new Dog(); =右边的运行时类型是Dog 小于 =左边的变量的类型Animal
Dog dog = (Dog)a1; =右边变量a1中的对象的运行时类型Dog 等于 =左边的变量的类型Dog
Animal a6 = new TaiDiDog(); =右边的运行时类型是TaiDiDog 小于 =左边的变量的类型Animal
Dog dog4 = (Dog) a6; =右边变量a6中的对象的运行时类型TaiDiDog 小于 =左边的变量的类型Dog
Animal a4 = new Animal(); =右边的运行时类型是Animal 等于 =左边的变量的类型Animal
Dog dog2 = (Dog)a4; =右边变量a4中的对象的运行时类型Animal 大于 =左边的变量的类型Dog(错误)
Animal a5 = new Cat(); =右边的运行时类型是Cat 小于 =左边的变量的类型Animal
Dog dog3 = (Dog) a5; =右边变量a5中的对象的运行时类型Cat 和 =左边的变量的类型Dog 无大小关系(错误)
9、如何保证向下转型,编译通过,运行就安全呢?
可以使用instanceof进行判断,来避免ClassCastException异常
语法格式:
对象/变量 instanceof 类型
该条件要编译和运行返回true的前提是:
(1)对象/变量的编译时类型和instanceof 后面的类型必须是父子类关系
(2)对象/变量的运行时类型 <= instanceof 后面的类型
public class TestClassCast {
public static void main(String[] args) {
Animal a1 = new Dog(); //a1从Dog类型向上转型为Animal,当然只发生在“编译时”
a1.eat();
// a1.watchHouse();//编译时,按照Animal处理
Dog dog = (Dog)a1;//a1从Animal类型向下转型为Dog,然只发生在“编译时”
dog.watchHouse();//编译时,按照Dog处理
System.out.println("--------------------------------------");
// Animal a2 = "hello"; //左边a2是Animal,右边"hello"是String类型,它俩之间没有父子类关系。所以无法向上转型/向下转型
// Animal a3 = (Animal)"hello";
System.out.println("--------------------------------------");
Animal a4 = new Animal();//本态引用,没有类型转换
// Dog dog2 = (Dog)a4; //编译时,把Animal类型的a4,转为了子类Dog类型。编译通过。 父类转为子类类型OK。
//上面的代码编译通过,因为它符合向下转型的语法,但是运行时报错:ClassCastException,类型转换异常。
//因为a4的运行时类型是Animal,它是不能被强制转换为Dog类型。
//反证法
// dog2.watchHouse();//假设上面的代码成功,该句代码就可以执行,但是通过dog2中记录对象的地址是无法找到watchHouse(),因为dog2中存储的是Animal的对象
System.out.println("--------------------------------------");
Animal a5 = new Cat();
// Dog dog3 = (Dog) a5;//编译时,把Animal类型的a5,转为了子类Dog类型。编译通过。 父类转为子类类型OK。
//上面的代码编译通过,因为它符合向下转型的语法,但是运行时报错:ClassCastException,类型转换异常。
//因为a5的运行时类型是Cat,它是不能被强制转换为Dog类型。
// dog3.watchHouse();
System.out.println("--------------------------------------");
Animal a6 = new TaiDiDog();//向上转型
Dog dog4 = (Dog) a6;//向下转型 编译时类型,a6是Animal类型,dog4是Dog
dog4.watchHouse();
System.out.println("--------------------------------------");
Animal a7 = new Animal();
Animal a8 = new Dog();
Animal a9 = new Cat();
Animal a10 = new TaiDiDog();
System.out.println(a7 instanceof Dog);//如果是true,说明a7可以强制为Dog false
System.out.println(a8 instanceof Dog);//如果是true,说明a7可以强制为Dog true
System.out.println(a9 instanceof Dog);//如果是true,说明a7可以强制为Dog false
System.out.println(a10 instanceof Dog);//如果是true,说明a7可以强制为Dog true
if(a7 instanceof Dog){
Dog dog5 = (Dog) a7;
System.out.println("a7被向下转型为Dog");
}
if(a8 instanceof Dog){
Dog dog6 = (Dog) a8;
System.out.println("a8被向下转型为Dog");
}
}
}
class Animal{
public void eat(){
System.out.println("吃");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("啃骨头");
}
public void watchHouse(){
System.out.println("看家");
}
}
class TaiDiDog extends Dog{
}
class Cat extends Animal{
}
8、多态引用时,关于成员变量的引用规则
是哪个成员变量的值,只看编译时类型。
9、多态引用时,对成员方法的引用规则
1、类中对成员变量的引用分为虚方法和非虚方法
虚方法:能够被重写的方法
非虚方法:除了虚方法,剩下的都是非虚方法
非虚方法包括:静态方法、私有方法、final的方法、实例初始化方法(),super.方法等
2、非虚方法,只看编译时类型
例1.public class TestExer1 {
public static void main(String[] args){
Animal a = new Cat();
a.eat();//吃鱼
}
}
class Animal {
public void eat(){
System.out.println("吃");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
例2.public class TestExer2 {
public static void main(String[] args) {
Father f = new Father();
Father s = new Son();
Father d = new Daughter();
MyClass my = new MyClass();
my.method(f); //f的编译时类型是Father
/*
method方法是虚方法。
my的编译时类型是MyClass,运行时类型也是MyClass。
(1)编译时,看父类MyClass,在MyClass中找,实参与形参最匹配的方法。
实参与形参匹配时,原则上只看编译时类型。f的编译时类型是Father
public void method(Father f) {
System.out.println("father");
}
(2)运行时,看子类,但是现在没有MyClass子类,即my这个变量的编译时和运行时都是MyClass
执行的还是刚刚在MyClass中找到的方法
public void method(Father f) {
System.out.println("father");
}
*/
my.method(s);
/*
method方法是虚方法。
my的编译时类型是MyClass,运行时类型也是MyClass。
(1)编译时,看父类MyClass,在MyClass中找,实参与形参最匹配的方法。
实参与形参匹配时,原则上只看编译时类型。s的编译时类型是Father
public void method(Father f) {
System.out.println("father");
}
(2)运行时,看子类,但是现在没有MyClass子类,即my这个变量的编译时和运行时都是MyClass
执行的还是刚刚在MyClass中找到的方法
public void method(Father f) {
System.out.println("father");
}
*/
my.method(d);
/*
method方法是虚方法。
my的编译时类型是MyClass,运行时类型也是MyClass。
(1)编译时,看父类MyClass,在MyClass中找,实参与形参最匹配的方法。
实参与形参匹配时,原则上只看编译时类型。d的编译时类型是Father
public void method(Father f) {
System.out.println("father");
}
(2)运行时,看子类,但是现在没有MyClass子类,即my这个变量的编译时和运行时都是MyClass
执行的还是刚刚在MyClass中找到的方法
public void method(Father f) {
System.out.println("father");
}
*/
}
}
class MyClass{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
public void method(Daughter f) {
System.out.println("daughter");
}
}
class Father{
}
class Son extends Father{
}
class Daughter extends Father{
}
3、虚方法:可能被重写的方法
情况一:
看.前面的对象的类型,.前面的对象编译时类型和运行时类型一致的,只在一个类中查找,找到谁执行谁。
找对应方法时,实参和形参只看编译类型和谁最匹配,如果没有最匹配的,就找能兼容
情况二:
看.前面的对象的类型,.前面的对象编译时类型和运行时类型不一致的,
(1)编译时看父类,到父类中查找是否有该方法
(2)运行时看子类,执行子类重写的方法,如果子类没有重写,还是执行父类中找到的方法
例三:
public class TestVirtualMethod {
public static void main(String[] args) {
Father f = new Son();
f.method();
}
}
class Father{
public void method(){
System.out.println("父类的方法");
}
}
class Son extends Father{
public void method(){
System.out.println("子类的方法");
}
}