Java笔记day12 多态和修饰符 作业

1、static

static修饰符可以修饰属性,方法,代码块

1.1、静态属性

在类中,使用static修饰的属性,就是静态属性

class Tets{
    static int s;
}

静态属性属于类,不创建对象也可以直接通过类名来访问,推荐使用类名访问

**注意,**非静态属性,是属于对象的,必须通过对象来访问

静态属性,属于类,存放在方法区中,是所有线程,对象共享的,只要在这个类中就可以使用

public class Demo{
    static int num = 5;
}

public static void main(String[] args){
    System.out.println(Demo.num);//输出为5
    Demo.num = 10;
    Demo demo1 = new Demo();
    Demo demo2 = new Demo();
    
    System.out.println(demo1.num);//输出结果为 10
    System.out.println(demo2.num);//输出结果为 10
    
    Demo.num = 20; 
    
    System.out.println(demo1.num);//输出结果为 20
    System.out.println(demo2.num);//输出结果为 20
    
    demo1.num = 30;
    System.out.println(demo1.num);//输出结果为 30
    System.out.println(demo2.num);//输出结果为 30
    
}

静态属性存储的位置是内存中方法区,当创建对象的时候,对象只会把类中的定义的非静态属性的信息存在堆空间中,静态属性不会静茹对象当中

类变量(static变量)

在Java虚拟机使用一类之前,它必须在方法区中为每一个非final类变量分配空间。非final类变量存储在定义它的类中。

静态属性的初始化

属性必须要进行初始化后才可以使用,初始化分系统为属性初始化赋值或者是显示赋值

属性的初始化时间:

  • 非静态属性:创建对象后,系统会自动给对象中的 非静态属性初始化赋默认值,正是因为这个原因,非静态属性只有在创建对象后,使用对象才能调用,实例化对象时候才会对静态属性进行初始化
  • 静态属性:类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值,所以,还没有创建对象的时候,静态属性已经加载到内存,这时候静态属性就已经完成了初始化赋默认值的操作。

静态属性是属于类的,只要类加载到内存了,就可以通过类名直接访问,非静态属性是属于对象的,只有创建对象,才可以通过对象访问

内存图:
image-20200729222619354

注意1, Demo类,被加载到内存的时候,静态属性num已经完成了默认的初始化赋值操作

注意2, 可以通过类名(Demo.num)来访问,它可以直接找到方法区中存储的静态变量num

注意3, 可以通过对象(demo.num)来访问,引用demo先找到堆区中的对象,再根据对象中存储的Demo.class信息,找到方法区中存储的静态变量num

注意4,通过上述可知,无论使用类名还是使用对象来访问静态变量num,都是访问的同一个num,但是官方推荐的是使用类名来访问更加合适。

注意,只有实例变量才会保存在对象中,并做初始化操作。静态变量保存在类中,并做初始化操作。

1.2、静态方法

static修饰的方法就是静态方法,与静态属性相同,可以直接使用类名也可以使用对象来调用这个方法,推荐使用类名

public class Demo{
    
    public static void test(){
        
    }
    
}

public static void main(String[] args){
	Demo.test();//推荐的方式
    
    Demo demo = new Demo();
    demo.test();//可以调用,但是不推荐
}

静态方法中不能调用类中的非静态方法或非静态属性

public class Test {
	static int s =10;
	int m  = 5;
	public void sya() {
		System.out.println("ssss");
	}
	public static void show() {
		System.out.println("11111");
	}
	public static void test() {
		this.show();//编译报错,不能在静态方法中使用this
		m =1;// 不能在静态方法中调用非静态属性
		sya();//不能在静态方法中调用非静态方法
	}
}

静态方法中,不能访问this,也不能访问非静态的属性和方法,这是因为在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this)就不能访问类中的非静态属性和非静态方法

非静态方法是可以调用静态属性和静态方法的,因为非静态方法都可以调用了,说明肯定已经创建对象了,同时也说明了这个类早就完成了类加载,那么类中的静态属性和静态方法,现在肯定是可以访问和调用的

在类加载的时候,jvm会优先个类中的静态属性做初始化,给类中的静态方法分配内存空间。类中的非静态属性初始化,分配空间需要等到创建实现的时候

所以类加载完成之后,就可以直接使用类名访问静态属性和静态方法

在创建对象完成之后,就可以通过对象来访问非静态属性和调用非静态方法

类中的构造器,可以个非静态熟悉进行初始化,但不能给静态属性初始化,这是因为只有创建对象的时候才会调用构造器,我们可以绕过创建对象的步骤,直接使用类名访问这个静态属性。

public class Demo {

    public static int num;

    public Demo(){
        num = 10;
    }

}

public static void main(String[] args){
    System.out.println(Demo.num);//输出结果为 0
}

在这里,编译不会出现问题,但是在运行中,构造器中的num = 10,并没有个给静态的int属性赋值,所以输出结果就是num的初始默认值。

1.3静态代码块

静态代码块,也叫静态初始化代码块,他的作用就是给类中的静态属性做初始化

public class Demo {

    public static int num;
//代码块是用一个花括号表示
    static{
        num = 10;
    }

}
public static void main(String[] args){
    System.out.println(Demo.num);//输出结果为 10
}

静态代码块执行

因为静态代码块没有名字,他们并不会主动调用,它会在类加载的时候,自动执行

静态代码块只会自动执行一次,因为在类加载到jvm内存的时候,静态代码块就会加载,一直沿用到类的生命周期结束。

静态属性,静态代码块,静态方法执行顺序

静态属性在静态代码块执行是会根据代码编写的顺序来决定的,建议静态属性的定义放在静态代码块前面,然后就再去执行静态方法。

匿名代码块

匿名代码块,作用就是给类中的非静态属性做初始化的

public class Demo {

    public int num;

    {
        num = 10;
    }

}
public static void main(String[] args){
    Demo demo = new Demo();
    System.out.println(demo.num);//输出结果为 10
}

注意,类中的构造器,既能给非静态属性进行初始化,又能配合new关键字进行对象的创建,所以匿名代码块使用的场景较少,它能完成的工作,使用构造器也一样可以完成。

匿名代码块执行的时刻:

由于匿名代码块没有名字,我们并不能主动调用,它会在创建对象的时候,构造器执行之前,自动执行

并且每次创建对象之前,匿名代码块都会被自动执行。

创建和初始化对象的过程:

Student s = new Student();

以这句代码为例进行说明:

  • 对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间
  • 执行类中的静态代码块
  • 堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
  • 调用Student的父类构造器
  • 对Student中的属性进行显示赋值,例如 public int age = 20;
  • 执行匿名代码块
  • 执行构造器代码
  • =号赋值操作,把对象的内存地址赋给变量s

注意1,子类重写父类的方法,在创建子类对象的过程中,默认调用的一定是子类中重写后的方法

注意2,非静态属性的显示赋值,是在 父类构造器执行结束之后子类中的匿名代码块执行之前 的时候

注意3,以上代码中,因为方法的重写,会调用子类中重写后的print方法,同时该方法恰好是在父类构造器执行中调用的,而这个时候子类中的name属性还没有进行显示赋值,所以是输出结果为null

注意4,如果此时在子类的匿名代码块中也输出name的值,那么就会显示tom,因为已经完成了属性的显示赋值

1.4 静态导入

在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名

注意,只有JDK1.5及以上版本,才可以使用静态导入

例如,没有使用静态导入的情况:

public class Demo {

    public void test(){
        System.out.println(Math.PI);//访问Math类中的静态属性PI,表示圆周率π
        System.out.println(Math.random());//访问Math类中的静态方法random(),生成随机数
    }

}

例如,使用静态导入的情况:

import static java.lang.Math.PI;
import static java.lang.Math.random;

public class Demo {

    public void test(){
        System.out.println(PI);
        System.out.println(random());
    }

}

注意,Arrays中的toString方法如果静态导入也不能直接使用,这是因为所有的类都会直接,间接的继承Object类,在这一个类中就给出了一个名为toString的方法,所以这时候我们想用toString方法就要指明是哪一个toString方法3

2、final

final可以修饰类,变量,方法

  • 用final修饰的类不能被继承,也就是说这个类是没有子类的。
  • 用final修饰的方法可以被子类继承,但是不能被子类的重写
  • 用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错

final修饰非静态成员变量

public class Person{
    private final int a;
}

对于这个final成员变量a,只有一次赋值的机会。

JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:

  • 声明的同时赋值
  • 匿名代码块中赋值
  • 构造器中赋值,此时还有额外要求:类中出现的所有构造器都要赋值,否则报错

final修饰静态成员变量

public class Person{
    private static final int a;
}

对于这个final静态成员变量a,只有一次赋值的机会。

JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:

  • 声明的同时赋值
  • 静态代码块中赋值

final修饰引用类型变量

final Student s = new Student();
//编译通过,可以修改s指向对象中的属性值
s.setName("tom");
s.setName("zs");

//编译报错,不能修改引用s指向的内存地址
s = new Student();

注意,此时final指的是,引用s的指向的对象不能改变,但是可以使用s来操作当前指向的对象属性和方法

3、abstract

abstrct 可以修饰类和方法

3.1 修饰方法

如果abstract修饰方法,那么该方法就是抽象方法。

抽象方法的特点:

  • 只有方法的声明
  • 没有方法的实现

例如,

//这就是一个普通方法,既有方法的声明,又有方法的实现
//方法的声明:public void test()
//方法的实现:{}
public void test(){}

//这就是一个只有声明没有实现的方法
public void test();

//这样的方法需要使用abstract修饰符来修饰,说明它是一个抽象方法
public abstract void test();

可以看出,声明方法的时候,加上abstract修饰符,并且去掉方法的大括号,同时结尾加上分号,那么该方法就是一个抽象方法。

3.2 修饰类

如果abstract修饰类,那么该类就是抽象类。

例如,

public abstract class Action{
    
}

抽象类和非抽象类的区别:

  • 抽象类使用了abstract修饰符,而非抽象类没有使用
  • 抽象类中可以编写抽象方法,而非抽象类中不能编写抽象方法
  • 抽象类不能进行实例化创建对象,而非抽象类可以实例化创建对象

抽象类和抽象方法的关系:

  • 抽象类中可以没有抽象方法
  • 有抽象方法的类,一定要声明为抽象类

3.3 意义

父类中的一个方法,如果被它的子类们重写,并且每个子类各自的实现又不相同,那么父类中的的这个方法,只有声明还有意义,而它的方法主体就变得没有任何存在的意义了。

这个时候,就可以把这个方法定义为抽象方法,只有方法的声明,没有方法的主体(也就是方法的实现)。

甚至在父类中,一些方法根本就没有办法进行实现,例如

public class Animal{
    public void run(){
        //这里应该编写动物奔跑的代码
    }
}

Animal类中,编写run方法主体的时候,遇到的问题就是:

虽然我们知道动物都会有奔跑的行为,但是不同的动物奔跑的方式是完全不一样的,那么在Animal中,我们并不知道当前是什么动物,这个run方法中的代码该如果编写呢?

其实,这里的代码无论怎么编写,都是不合适的,毕竟在Animal中,我们并不知道要奔跑的动物到底是哪一种,并且这个run方法将来一定是会被子类重写的,既然如此,那么还不如就索性把run定义为抽象方法,只有方法的声明, 没有方法的实现,将来让子类去实现run方法好了。

例如,

public class Cat extends Animal{
    //子类重写父类中的run方法,这时候就变得非常明确了,就是一只猫走路的方式
    public void run(){
        System.out.println("我是一只猫,我踮起脚尖,优雅的走着猫步");
    }
}

注意,从语法上讲,我们依然可以在Animal中对run方法进行实现,随便写点代码好了,反正又不会被调用,因为run一定是会被子类重写的,但是这样写代码,就没有了设计的意义了。

所以,很多时候我们不能只看代码的语法是否正确,而且要关注这样的代码设计是否有意义!!

既然Animal类中的run方法被定义为了抽象方法,那么Animal类就一定要声明为抽象类

public abstract class Animal{
    public abstract void run();
}

思考:既然抽象类不能被实例化创建对象,那么这个类有什么用?

抽象类是用来被子类继承的,子类继承抽象类,并且实现抽象类中的抽象方法。

所以,当前我们遇到一个抽象类的时候,第一反应应用是这个类肯定需要被继承,然后实现里面的抽象方法,或者重写里面的普通方法。

注意,实现父类中的抽象方法 和 重写父类中的普通方法,只是说法不同,但是它们的语法要求,操作方式完全是一样的,可以直接把实现抽象方法当做重写方法来看。

思考:抽象类不实例化创建对象,那么抽象类中是否有构造器?

有构造器,这个构造器是让子类调用的,子类继承父类,子类创建对象的时候,会先调用父类的构造器!

思考:如果我们要编写一个类,只想让别人继承它并重写指定方法,那么我们该如何设计?

将这个类定义为抽象类即可,同时如果想要求别人继承的时候,一定要重写某个方法的话,只要把这个方法定义为抽象方法,别人在继承这个类的时候,就一定要会对这个抽象方法进行实现!

思考:子类继承抽象父类,子类是否可以不实现父类中的抽象方法?

可以实现,子类继承抽象父类,子类可以选择实现父类中所有的抽象方法,如果有任何一个抽象方法没有被子类实现,那么这个子类也要将自己声明为抽象类,那么这个子类也就只能等待,再来一个子类继承自己,去实现剩余没有实现的抽象方法,直到所有抽象方法都被实现为止。

4、多态

相同类型的不同对象,调用同一个方法,最终执行结果是不同的

例如,猫、狗、猎豹都属于动物类,它们都有“跑”这个共同的行为,但是它们各自跑起来的方式又是不一样的。由此可见,不同的对象,进行同一个行为,但是它们的表现出来的确实不同的形态。

动物 动物1 = new 猫();

java中的多态,就是来描述这种情况的。

多态的体现:

  • 父类引用指向子类对象
  • 父类引用也可以接受子类对象

多态的前提:

  • 子类继承父类
  • 子类重写父类中的方法
  • 父类的引用指向子类对象

注意,类实现接口,这也是一直特殊形式的继承,多态也可以体现在类和接口的关系中。

注意:多态只针对方法

例如,

public class Person {

    public void sayHello(){
        System.out.println("你好!");
    }

}

class Student extends Person{
    public void sayHello(){
        System.out.println("hello!我是一名酷酷的学生");
    }
}

class Teacher extends Person{
    public void sayHello(){
        System.out.println("hi!我是一名酷酷的老师");
    }
}

public static void main(String[] args) {
    //声明父类的引用
    Person person;

    int random = (int) (Math.random()*10);

    //根据随机数的情况,让父类引用person指向不同的子类对象(Student对象或者Teacher对象)
    if(random%2 == 0){
        person = new Student();
    }
    else{
        person = new Teacher();
    }
	
    //使用person引用,调用子类对象中重写的方法
    //关键点在于,在调用sayHello方法的时候,引用person指向的对象是谁
    person.sayHello();

}

同一类型(Person)的引用,指向不同的子类对象(Student或者Teacher),调用同一个方法(sayHello),最后是不一样的表现形式(执行结果不同)

例如,

public class Animal {
	public void eat(){}
}

class Cat extends Animal {
    public void eat() {
    	System.out.println("我是喵咪,我喜欢吃鱼");
    }
}

class Dog extends Animal {
    public void eat() {
    	System.out.println("我是狗仔,我喜欢吃骨头");
    }
}

public static void main(String[] args) {
    
    //多态形式,创建对象
    //父类引用指向子类对象
	Animal a = new Cat();
    
    //调用的是Cat中重写的eat方法
    //因为这时候引用a指向的是Cat类型对象
    a.eat();
    
    //父类引用指向另一个子类对象
    a = new Dog();
    
    //调用的是Dog中重写的eat方法
    //因为这时候引用a指向的是Dog类型对象
    a.eat();
    
    
}

注意,一个父类型的引用,可以指向它的任何一个子类对象

程序中使用多态的好处:

有一个Game类,里面有俩个方法,可以分别运行篮球游戏和足球游戏。

第一种情况,不使用多态

public class Game {

    public void start(BasketBall basketBall){
        basketBall.play();
    }

    public void start(Football football){
        football.play();
    }

}

class BasketBall{
    public void play(){
        System.out.println("篮球游戏开始初始化...");
    }
}

class Football{
    public void play(){
        System.out.println("足球游戏开始初始化...");
    }
}


public static void main(String[] args) {

    Game game = new Game();

    BasketBall basketBall = new BasketBall();
    game.start(basketBall);

    Football football = new Football();
    game.start(football);


}

在这种情况下, 如果多了一种乒乓球游戏要运行,代码代码要如果修改?

代码修改如下:

public class Game {

    public void start(BasketBall basketBall){
        basketBall.play();
    }

    public void start(Football football){
        football.play();
    }
    
    //Game中新增运行乒乓球游戏的方法
    public void start(PingPangBall pingPangBall){
        pingPangBall.play();
    }

}

class BasketBall{
    public void play(){
        System.out.println("篮球游戏开始初始化...");
    }
}

class Football{
    public void play(){
        System.out.println("足球游戏开始初始化...");
    }
}

//新增乒乓球类
class PingPangBall{
    public void play(){
        System.out.println("乒乓球游戏开始初始化...");
    }
}

public static void main(String[] args) {

    Game game = new Game();

    BasketBall basketBall = new BasketBall();
    game.start(basketBall);

    Football football = new Football();
    game.start(football);
    
    //新增创建乒乓球对象
    PingPangBall pingPangBall = new PingPangBall();
    //新增调用game中的对应的运行乒乓球游戏的方法
    game.start(pingPangBall);


}

思考,如果一直不断的新增游戏的种类,代码修改的起来是否会很麻烦?

第二种情况,使用多态

public class Game {
	//这里只需要定义一个方法,参数类型是Ball类型
    //这个Ball是父类,它的引用可以指向任何一个子类对象
    public void start(Ball ball){
        ball.play();
    }

}

//父类
class Ball{
    public void play(){}
}

//子类
class BasketBall extends Ball{
    public void play(){
        System.out.println("篮球游戏开始初始化...");
    }
}

//子类
class Football extends Ball{
    public void play(){
        System.out.println("足球游戏开始初始化...");
    }
}

public static void main(String[] args) {

    Game game = new Game();

    //声明父类的引用
    Ball ball;
	
    //指向一个子类对象
    ball = new BasketBall();

    //这里想要切换游戏,只需要让引用ball执行对应的子类对象即可
    //ball = new FootBall();

    game.start(ball);


}

在这种情况下, 如果多了一种乒乓球游戏要运行,代码代码要如果修改?

代码修改如下:

public class Game {
	//这里只需要定义一个方法,参数类型是Ball类型
    //这个Ball是父类,它的引用可以指向任何一个子类对象
    public void start(Ball ball){
        ball.play();
    }

}

//父类
class Ball{
    public void play(){}
}

//子类
class BasketBall extends Ball{
    public void play(){
        System.out.println("篮球游戏开始初始化...");
    }
}

//子类
class Football extends Ball{
    public void play(){
        System.out.println("足球游戏开始初始化...");
    }
}

/*整个代码中,基本只有这里是新增的,其他地方不需要改动*/
/*新增乒乓球类*/
class PingPangBall extends Ball{
    public void play(){
        System.out.println("乒乓球游戏开始初始化...");
    }
}

public static void main(String[] args) {

    Game game = new Game();

    //声明父类的引用
    Ball ball;
	
    //指向一个子类对象
    ball = new BasketBall();

    //这里想要切换游戏,只需要让引用ball执行对应的子类对象即可
    //ball = new FootBall();
    //ball = new PingPangBall();

    game.start(ball);


}

可以看出,这时候想新增一个球类游戏,并且去运行它,几乎不需要修改什么代码,只需要新增这个类即可

思考:下面这个方法的参数,都可以接收哪些对象?

public void test(Object obj){
    
}

多态中的成员访问特点之成员变量

编译看左边(父类),运行看左边(父类)

多态中的成员访问特点之成员方法

编译看左边(父类),运行看右边(子类)。

静态方法

  • 编译看左边(父类),运行看左边(父类)。
  • (静态和类相关,算不上重写,所以,访问还是左边的)
  • 只有非静态的成员方法,编译看左边,运行看右边

5、 instanceof

在使用多态的情况下,instanceof关键字就显得特别重要,因为它能告诉我们,当前父类的引用,到底是执行的哪一个子类对象

例如,

public class Person {

}

class Student extends Person{
    
}

class Teacher extends Person{
    
}

在这种情况下, 假设有一个test方法:

由多态可知,test方法的参数p,既可以接收Student类型的对象,也可以接收Teacher类型的对象,那么在这个test中,如何能知道,将来到底接收到的是哪一个类型的对象呢?

public void test(Person p){
   //引用p到底是指向的Student对象还是Teacher对象呢?? 
}

通过instanceof关键字进行判断即可知道引用p到底接收的是一个类型的对象:

public void test(Person p){
   if(p instanceof Student){
       //说明p指向的是Student类型对象
   }
   else if(p instanceof Teacher){
       //说明p指向的是Teacher类型对象
   } 
    
}

思考,假设通过instanceof关键字的判断,知道了引用p到底指向的是哪个类型对象,接下来又能做什么

6、引用类型的转换

多态中的转型分为向上转型向下转型两种:

  • 向上转型(子类转父类)

    多态本身就是将子类的对象赋值给父类的引用,这就是一个自动向上转型的过程(类型自动转换)

    例如,Person p = new Student();

  • 向下转型(父类转子类)

    父类类型向子类类型转换,是向下转换的过程。(类型强制转换)

    例如,

    Person p = new Student();
    Student s = (Student)p; //向下转型
    

为什么需要做向下转型:

当使用多态方式调用方法时,编译器会先检查父类中是否有该方法,如果父类中没有,则编译错误

在这种情况下,即使子类中有这个方法,也是无济于事,因为编译器这一关都过不去,更不要说去运行了。

也就是说,在使用多态的情况下,父类引用是无法调用到子类中独有的方法的

例如,

public class Person {
	public void sayHello(){}
}

class Student extends Person{
	
    //注意,这是子类中单独定义的方法,和父类无关
    public void study(){}
        
}

public static void main(String[] args) {
    
    //多态,父类的引用指向子类对象
    Person p = new Student();
    
    p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
    
    //编译报错
    //因为引用p是Person类型的,而Person类型中就没有定义这个study方法
    //编译器检查到Person中没有这个方法就直接报错了
    p.study();
    
}

在这个情况下,只能将引用p向下转型,转成Student类型,才能调用到study方法:

public static void main(String[] args) {
    
    //多态,父类的引用指向子类对象
    Person p = new Student();
    
    p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
    
    Student s = (Student)p;
    
    //编译通过
    //因为引用s是Student类型的,而Student类型中真的有这个study方法
    s.study();
    
    
}

引用类型强制转换时的异常:

在类型强制转换的过程中,可能会遇到类型转换异常,例如:

public class Person {

}

class Student extends Person{
     //注意,这是子类中单独定义的方法,和父类无关
    public void study(){}
}

class Teacher extends Person{
    
}

public static void main(String[] args) {
    
    Person p = new Teacher();
    
    //编译通过,运行报错!
    //错误类型是:ClassCastException
    Student s = (Student)p;
    s.study();
    
}

上面代码运行报错的原因,是因为运行过程中,强制转换的时候,引用p实际指向的对象是Teacher类型的,在这种情况下,它是不能转为Student对象的,因为Teacher对象和Student类型没有任何子父类的关系!

那么这句代码 Student s = (Student)p;想转换成功,最终运行不报错,只要一直情况,那就是在转换的时候,引用p指向的对象,真的就是Student类型的对象才可以。

那么如果取得引用p指向的对象真的就是Student类型的呢? 使用关键字instanceof进行判断即可:

public static void main(String[] args) {
    
    Person p = new Teacher();
    
    //如果引用p真的是指向Student类型的对象,那么才进行类型的强制转换
    //避免在强制转换的过程中出现ClassCastException类型的异常
    if(p instanceof Student){
        Student s = (Student)p;
    	s.study();
    }
    
    
}
7、作业

1.以下程序是否报错,如果报错,为什么,不报错结果是多少
class A {
public int func1(int a, int b) {
return a - b;
}
}
class B extends A {
public int func1(int a, int b) {
return a + b;
}
public int test1(int a) {
return a;
}
}
public class ChildClass {
public static void main(String[] args) {
A a = new B();
B b = new B();
B b1 = (B)a;
System.out.println(“Result=” + a.func1(100, 50));
System.out.println(“Result=” + b.func1(100, 50));
System.out.println(“Result=” + a.test1(100));
System.out.println(“Result=” + b.test1(100));
System.out.println(“Result=” + b1.test1(100));
}
}
//150
//150
//编译报错 要向下转型
//100
//100
3.输出结果。

public class SubClass extends BaseClass{
public int a=6;
public static void main(String[] args) {
SubClass sub=new SubClass();
sub.accessBase();
sub.accessOwner();
}
public void accessOwner() {
System.out.println(a);
}
public void accessBase() {
System.out.println(super.a);
}
}
class BaseClass{
public int a=5;
}
//6
//5

4.输出结果

public class SubClass extends BaseClass{
	public String a="hello";
	public void test(){
	System.out.println(“子类覆盖父类的方法");
	}
	public void sub(){
	System.out.println("子类特有的方法");
	}
	public static void main(String[]args){
		BaseClass base=new BaseClass();
		System.out.println(base.a); 
		base.base();
		base.test(); 
		SubClass sc=new SubClass();
		System.out.println(sc.a);  
		sc.base();
		sc.test();
		BaseClass b=new SubClass();
		System.out.printin(b.a);//属性看左边
		b.base();
		b.test(); 
		b.sub()  
	}
}
class BaseClass{
	public int a=5;
	public void base(){
	System.out.println("父类的构造方法");
	}
	public void test(){
	System.out.println("父类的被覆盖方法");
}
//5

//父亲的构造方法
//父亲的被覆盖方法
//hello
//父类的构造方法
//子类覆盖父类的方法
//hello
//父类的构造方法
//子类覆盖父类的方法
//报错
5.输出结果

package com.briup;
public class SubClass extends BaseClass{
	public String a="hello";
	public void test() {
		System.out.println("子类覆盖父类的方法");
		System.out.println(a.length());
	}
	public static void main(String[] args) {
		new SubClass();
	}
}
class BaseClass{
	public BaseClass() {
		test(); 
	}
	public void test() {
		System.out.println("将被子类重写的方法");
	}
}

//子类覆盖父类的方法
//编译不会报错,运行会报错 因为此时会先优先调用父类的构造器,而父类构造器中调用了test方法,而test方法被重写了,调用的是重写后的test方法,而此时String a还没有被显示赋值还是null,所以就会空指针异常

6.输出结果

public clas Test
{
	{
	 a=6;
	}
	int a=9; 
	int b=1;
	{
	   b=2;
	}
	public static void main(String[] args){
	System. out. printin(new Test().a); 
	System. out. printin(new Test().b); 
	}
)

//匿名代码块和显示赋值,按照代码编写顺序执行
//9
//2

7.编译运行,结果是()。

class Parent{
    public void count( ){
        System.out.println(10%3); 
    }
}
public class Test extends Parent{
    public void count(int i){
        System.out.println(10%i);
    }
    public static void main(String[]args){
        Parent p = new Test( );
        p.count(3); 
    }
}

//报错 因为父类中没有这个方法 编译看左边

8.如果在某个方法中访问名为a的成员变量,但没有显示指定调用者,则系统查找a的顺序为。
静态代码
赋值初始化
显示赋值
匿名代码块
构造器

9.实现一个单例类,该类只会产生一个实例对象。
提示:可以将构造方法私有化
public class Test {

private Test() {
	super();
}

public static void main(String[]args){
	Test test = new Test();

}
}
class A extends Test{
//此时A会编译报错
}
11.static成员变量和非静态成员变量的区别?
1、
静态成员变量: 方法区的静态区域
非静态成员变量: 堆内存中的对象空间里面
2、
静态成员变量: 在数据类型前面多了一个static修饰
非静态成员变量: 没有static修饰
3、
静态成员变量: 在类加载的时候,类加载完成,就分配完空间;直到类被卸载时空间被回收
非静态成员变量: 创建对象的时候分配空间; 对象变为垃圾空间被回收的时候被销毁
4、
静态成员变量: 直接通过类名使用
非静态成员变量: 必须通过对象使用
5、
静态成员变量: 对该类的所有对象都有影响;
非静态成员变量: 只对一个对象有影响

12匿名代码块和静态代码块的作用;
静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作
非静态代码块,给非静态属性做初始化操作
13.abstract的作用
可以修饰类和方法
不能修饰属性和构造方法
abstract 修饰的类是抽象类,需要被继承
abstract 修饰的方法是抽象方法,需要子类被重写
14.final关键字的作用
被final修饰的类不能被继承
被final修饰的方法不能被重写
被final修饰的变量不能被再次赋值
15.抽象类的特点,抽象类及抽象方法可以被final修饰符修饰吗?

  • 抽象类使用了abstract修饰符,而非抽象类没有使用

  • 抽象类中可以编写抽象方法,而非抽象类中不能编写抽象方法

  • 抽象类不能进行实例化创建对象,而非抽象类可以实例化创建对象
    抽象类及抽象方法不可以被final修饰符修饰
    抽象类不能被实例化,所以要想使用抽象类中的方法就必须被继承,而被final修饰的类不能被继承
    抽象类中的抽象方法需要在子类中重写,如果被final修饰的方法无法被重写
    16.程序输出

    public class Test {
      public Test() {
          system.out.println("构造器");
      }
      public void info() {
          System.out.println("info");
      }
      static {
          System.out.println("test  static  1");
      }
      public static void main(String[] args) {
          new Test().info();
      }
      static {
          System.out.println("test  static  2");
      }
    

    }
    //test static 1
    //test static 2
    //构造器
    构造器接受代表一个对象创建完成
    //info
    17.程序输出

    public class Test {
    public static void main(String[] args) {
    Zi z = new Zi();
    }
    }
    class Fu {
    static {
    System.out.println(“静态代码块Fu”);
    }

      {
          System.out.println("构造代码块Fu");
      }
    
      public Fu() {
          System.out.println("构造方法Fu");
      }
    

    }

    class Zi extends Fu {
    static {
    System.out.println(“静态代码块Zi”);
    }
    {
    System.out.println(“构造代码块Zi”);
    }

      public Zi() {
          System.out.println("构造方法Zi");
      }
    

    }
    //静态代码块Fu
    //静态代码块Zi
    //构造代码块Fu
    //构造方法Fu
    //构造代码块Zi
    //构造方法Zi
    18.程序输出

    public class B
    {
    public static B t1 = new B();
    public static B t2 = new B();
    {
    System.out.println(“构造块”);
    }
    static
    {
    System.out.println(“静态块”);
    }
    public static void main(String[] args)
    {
    B t = new B();
    }
    }
    //构造块
    //构造块
    //静态块
    //构造块
    19.输出结果

    public class FinalMethodTest{
    //pulbic final void test(){}
    public static void main(String[] args) {
    FinalMethodTest test = new Sub();
    test.test();
    }
    private final void test(){
    System.out.println(“父类”);
    }

    }
    class Sub extends FinalMethodTest{
    public void test(){
    System.out.println(“子类”);
    }
    }
    //父类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值