子类父类方法执行顺序?多态中和子类打印不一样;
子类在实现父类方法的时候没有用super关键字进行调用也会先执行父类的构造方法吗?
是的,当子类实例化时,先执行父类的构造方法,再执行子类的构造方法。即使在重写父类方法时未使用 super 关键字调用父类的方法,在实例化子类时仍然会先执行父类方法。因此,在编写子类构造方法时,最好使用 super() 显式地调用父类的构造方法,以确保正确的初始化顺序和继承关系。
一、Java中什么时候需要重写?
在 Java 中,当子类继承了父类并希望修改、新增或删除父类的某些行为时,就需要进行方法重写。方法重写是指子类中定义一个和父类相同方法签名(即方法的名称、参数列表和返回值类型都相同)的方法,并重新实现其功能。通过重写父类中的方法,子类可以覆盖父类的实现,从而使具有多态性的程序能够按照不同的场景调用正确的方法。通常情况下,重写发生在以下情形中:
- 子类希望修改从父类继承的方法,以满足子类特定的需求;
- 子类希望添加额外的功能或逻辑,并调用原来父类的方法实现这部分功能;
- 父类中的方法已经过时或不再适用于子类的场景,需要将其废弃或替换。
需要注意的是,重写方法时,访问修饰符不能比父类低,返回类型应该与父类相同或其子类,方法名和参数列表必须完全匹配,抛出的异常应该是父类方法可能抛出的异常的子集。否则会导致编译错误。
二、接口和抽象类:
在Java中,接口和抽象类都是用于实现抽象化编程的机制,但它们之间也存在一些不同:
- 实现方式:抽象类使用
abstract
关键字声明,可以包含非抽象方法,并且需要通过继承来使用;而接口使用interface
关键字声明,不能包含任何具体的方法实现,只能定义方法签名。接口需要通过实现来使用。 - 继承关系:一个类只能继承一个抽象类,但可以实现多个接口。这是因为Java中单继承的限制,子类只能有一个直接的父类。继承一个抽象类只能满足部分的重用,若想要满足复杂的多重继承需求,则应该使用接口。
- 权限修饰符:抽象类中的方法可以定义为public、protected、或没有修饰符,而接口中的方法只能定义为public。这是因为接口的主要目的是表达行为,不存在属性及其实现。
- 接口还可以包含常量,这些常量自动成为公共的静态常量(即
public static final
),而抽象类则不能包含常量。 - 对于抽象类,子类必须实现或覆盖所有的抽象方法,否则子类也必须被声明为抽象类。而对于接口,实现类必须实现所有的方法定义,否则需被标记为抽象类或该接口的子接口。
综上,抽象类和接口都是在面向对象程序设计中用于实现共性与个性分离,提高代码重用性和可扩展性的机制,但它们的适用场景和语法规范有所不同。
三、static 修饰类的变量或方法,而不是方法的局部变量
是的,static 关键字可以用来修饰类的变量和方法,但不能用来修饰方法中的局部变量。具体如下:
- 对于类成员变量:使用 static 修饰的变量被称为静态变量或类变量,其生命周期与类相同,不依赖于任何对象,可以通过类名直接访问。所有该类(或其子类)的实例共享静态变量,对静态变量的修改会影响到所有实例的值。
- 对于类方法:使用 static 修饰的方法被称为静态方法,它不依赖于任何对象而存在,可以通过类名直接调用。由于静态方法不需要创建对象就可以被调用,因此无法访问非静态成员变量和方法,也无法在方法内部使用 this 关键字。
- 对于局部变量:static 不能用于修饰方法中的局部变量,这是因为局部变量的生命周期只存在于方法执行期间,无法存储在堆中。如果将局部变量声明为 static ,则编译器会报错。
总之,static 的作用目标是类级别的,意味着其作用范围是整个类,而不是某个对象。静态成员和方法提供了一种方便的方式来存储和共享信息,但过度使用可能会导致代码紧耦合和测试困难。
例子:以下代码展示了 static 的几种用法示例:
java复制代码
public class Example {
// 静态变量,被类的所有实例共享,并初始化为0
static int num = 0;
// 普通变量,每个实例独立存储
int id;
// 静态方法,能直接通过类名调用,累加静态变量num的值
public static void increaseNum() {
num++;
}
// 实例方法,只能通过实例对象调用,设置实例变量id的值
public void setId(int id) {
this.id = id;
}
// 静态代码块,在类加载时执行,初始化静态成员属性
static {
num = 10;
}
public static void main(String[] args) {
// 直接通过类名访问静态变量
System.out.println("num = " + Example.num);
// 创建两个Example实例,并设置它们的id
Example obj1 = new Example();
obj1.setId(1);
Example obj2 = new Example();
obj2.setId(2);
// 调用实例方法时需要先创建对象,使用this引用来操作实例变量
System.out.println("obj1.id = " + obj1.id);
System.out.println("obj2.id= " + obj2.id);
// 直接通过类名调用静态方法
Example.increaseNum();
System.out.println("num = " + Example.num);
}
}
在上述示例中,静态变量 num 被所有该类实例共享,静态方法 increaseNum 可以直接通过类名调用。此外,示例中还使用了静态代码块 static {...} 用于初始化静态成员属性,在类加载时只会执行一次。
四、Java中arraylist会报什么错误
Java 中的 ArrayList 可能会出现多种错误,以下是一些可能的错误以及原因:
- IndexOutOfBoundsException 索引越界异常: 当使用索引访问 ArrayList 时,如果索引超出了可访问的范围,就会抛出此异常。例如,在空 ArrayList 上尝试访问第一个元素(索引为0)。
- NullPointerException 空指针异常: 如果尝试将 null 添加到 ArrayList 中,或者在空的 ArrayList 中进行访问或操作方法调用,则可能会导致此异常,例如,尝试通过空的 ArrayList 访问其迭代器、尝试从 null 参数创建新 ArrayList 等。
- ConcurrentModificationException 并发修改异常:在迭代 ArrayList 过程中,如果同时对该列表进行修改操作,如增加、删除、更改元素等不当操作,就会抛出此异常。要解决此异常,应该使用迭代器或与线程同步机制协作。
- ClassCastException 类型转换异常: 在向 ArrayList 引用类型数据添加元素时,有可能遇到类型转换失败的情况。例如,如果您试图将整数添加到存储字符串的 ArrayList 上,就会抛出此异常。请确保正确使用泛型,并注意强制类型转换。
还有其他一些可能的异常,这里只列举了一些常见的情况。为了避免出现这些异常,请经常检查和正确使用 ArrayList 的相关方法,以及了解方法的含义和参数。
五、一个抽象类中能否既定义抽象方法又定义非抽象方法?能,但子类不能继承抽象方法,
是的,一个抽象类中既可以定义抽象方法,也可以定义非抽象方法。而子类继承抽象类时必须实现其所有抽象方法,不需要实现非抽象方法。
原因在于抽象方法是没有方法体的,只有声明和返回类型,意味着它们不存在默认实现。子类必须提供实现以满足该抽象方法,否则这个子类也必须被声明为抽象类,从语法上不能被实例化。非抽象方法已经具有默认的实现方式,它们可以直接在抽象类中定义,但子类也可以选择重写它们来实现自己的行为。当然,子类也可以直接使用抽象类中定义的非抽象方法,而无需覆盖它们。
在设计抽象类时,通常使用抽象方法来声明子类必须实现的“协议”,从而让子类实现自己特定的业务逻辑,同时提高代码的可读性和可维护性,而非抽象方法则用于共享一些公共代码或功能。
为什么子类不能继承抽象方法
子类可以继承定义在父类中的抽象方法。但是,如果一个子类不实现从其抽象父类继承的所有抽象方法,则该子类也必须被声明为抽象类。因此,子类需要实现其所继承的任何抽象方法,否则子类就无法被实例化,这是编译器要求的。
具体原因在于抽象方法没有默认的实现方式,只有声明和返回类型,意味着该方法的具体实现需要在子类中实现。在Java语言中,如果一个类继承了一个抽象类,并且它没有实现从该抽象类继承的抽象方法,这个类必须被声明为抽象类,因为抽象类不能够被实例化。这样设计是为了鼓励开发人员正确实现抽象类中的行为而不是依赖于默认行为或超类的行为。这种约束在软件开发中通常被认为是良好的做法,能够帮助开发者更好地理解和维护他们的代码。
六、多态调用成员的特点:编译看左边,运行看右边的例子,什么意思
"编译看左边,运行看右边"是一个Java中多态性的特点,实际上也被称为"动态绑定"。
在Java中,如果一个类继承自另一个类,那么它就可以使用父类中定义的方法。当子类重写了父类中的方法,并且我们通过子类的对象来调用该方法时,程序会选择子类中已经重写的方法并执行它。
这时候,Java编译器会根据引用变量的类型来决定该调用哪一个方法(即编译看左边),而实际执行方法的时候,由于子类已经重写了父类的方法,因此会执行子类中的方法(即运行看右边)。
举个例子:
class Animal {
public void move() {
System.out.println("Animal is moving...");
}
}
class Dog extends Animal {
@Override
public void move() {
System.out.println("Dog is running...");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
animal.move(); // 调用子类Dog中的move()方法,输出Dog is running...
}
}
在上面的例子中,我们定义了Animal类和Dog类,其中Dog类继承自Animal类,并且重写了其move()方法。在主函数中,我们创建了一个Animal类型的对象animal,并将其赋值为一个Dog类型的对象。由于animal是Animal类型的引用变量,因此在编译时编译器会按照Animal类型处理,而在运行时由于实际上animal对象指向了Dog类型的对象,因此程序运行时会执行Dog中定义的move()方法,输出"Dog is running..."。
七、正确的接口格式有哪些
Class A interface B
一个正确的接口设计应该具备以下几个方面的要求:
- 接口名称应该准确、简洁,并能够反映它所代表的含义。
- 接口中定义的方法名称应明确、简单易懂,并且符合常见命名规范,如遵循驼峰式命名法等。
- 参数数量和类型应该尽可能少而简单,最好不超过四个。同时,在定义参数时应该清晰明确每个参数的用途和含义。
- 返回值类型应该是清晰明确、不会引起歧义的类型。
- 接口方法应该尽可能保持高内聚、低耦合的特性,即只关注具体的业务内容,而不关心其它功能模块的实现细节。
- 在方法调用失败或出错时,应该通过异常传递错误信息,并让调用者对异常进行处理。
- 为了提高代码的可读性和可维护性,应该在接口中加入必要的文档说明,包括接口的使用方法及其限制条件。
基于以上要点,一个规范的接口格式应该包括:接口名称、方法名称、参数列表、返回值类型和可能抛出的异常等信息,同时还需要配合文档说明,让调用方更容易地理解并使用该接口。
八、无参构造方法:
public class MyClass{
public MyClass(){
System.out.println("Hello, Constructor!");}
public static void main(String[] args){
MyClass myObject = new MyClass();
}
}
这个程序中,MyClass
类有一个无参构造函数,在构造函数中输出了一句话。在 main
函数中,创建了一个 MyClass
的对象 myObject
。由于没有传入参数,因此会调用类的无参构造函数。运行这个程序,将会看到控制台输出 "Hello, Constructor!"。
如果构造函数有参数,那么在创建对象时需要传入相应的参数。例如:
构造函数传入参数
public class MyClass {
private int number;
public MyClass(int n) {
this.number = n;
System.out.println("Number is set to " + this.number);
}
public static void main(String[] args) {
MyClass myObject = new MyClass(10);
}
}
MyClass
类的构造函数带有一个 int
类型的参数,用于初始化类的私有属性 number
。在 main
函数中,在创建 MyClass
对象时传入了一个整数 10。程序在运行时会调用 MyClass
的构造函数,并输出 "Number is set to 10"。
九、向上向下强制转化
子类能向父类强制转换吗
在 Java 中,子类可以向父类进行强制类型转换。这种类型转换被称为向上转型(upcasting),因为它是将一个对象从派生类转换为其基类的过程。
在向上转型中,派生类对象被看做是其基类的一种类型,因此可以将一个派生类对象赋值给一个基类引用变量。例如:
class Animal {}
class Dog extends Animal {}
Dog d = new Dog(); // 创建 Dog 类对象
Animal a = (Animal) d; // 向上转型,将 Dog 类对象转换成 Animal 类对象
在上面的代码中,我们可以看到 Dog 是 Animal 的一个子类。首先我们声明了一个 Dog 对象 d,然后将其强制转换成为 Animal 类型,并将结果赋值给 Animal 对象 a,这个过程就是向上转型。通过这种方式转型之后,我们就可以通过 a 引用访问 Dog 对象的所有继承自 Animal 类的属性和方法,但是无法直接访问 Dog 类特有的属性和方法。
需要注意的是,向上转型可能会导致部分信息的丢失,因为向上转型后原来的派生类特有的方法和属性将无法被访问。另外,在运行时如果尝试将一个基类对象转换成一个非基类对象,则会抛出 ClassCastException 异常。
父类能向子类强制转换吗
在 Java 中,父类不能向子类进行强制类型转换。这种类型转换被称为向下转型(downcasting),它是将一个对象从基类转换为派生类的过程。
由于派生类可能有一些额外的方法和属性,而基类没有,因此在对基类实例进行类型转换时,只能针对那些被向上转型的子类对象算作合法的向下转型对象。当一个基类实例被转换成一个与其本身类型无关的子类时,会导致编译器错误或者运行时异常。
示例如下:
class Animal {}
class Dog extends Animal {}
Animal a = new Animal();
Dog d = (Dog) a; // 这里会抛出 ClassCastException 异常
在上面的代码中,我们先定义了一个 Animal 对象 a,然后尝试将其强制转换成为 Dog 对象,并赋值给 Dog 对象 d。但由于 a 引用指向的是 Animal 类对象,而不是 Dog 类对象,因此这个转换将会在运行时引发 ClassCastException 异常。
需要注意的是,虽然使用 instanceof 运算符可以判断一个对象是否能够强制转换成某一类型,但该运算符也不应该在每个地方都被使用,因为过度使用 instanceof 通常意味着代码结构上的不良设计,可以使用多态方案或浅拷贝深拷贝等方式替代。
十、抽象方法合法定义!不能创建new实例,类和抽象方法都要带abstract
在 Java 中,抽象方法是指声明而未实现的方法,它只有声明部分没有具体的实现内容,并且在声明时必须使用 abstract 关键字修饰。抽象方法只能存在于抽象类或者接口中,这也就是说,如果一个类中含有抽象方法,则该类必须被声明为抽象类。
抽象方法的语法格式如下:
public abstract void methodName(parameterList);
需要注意的是,在定义抽象方法时不能包含方法主体,即不能使用花括号 {} 来定义方法体。抽象方法没有实现,需要等待后续继承该抽象类或实现该接口的子类来实现其具体功能。
以下是一个抽象类和抽象方法的示例:
abstract class Shape {
protected int x, y;
public void move(int newX, int newY) {
this.x = newX;
this.y = newY;
}
abstract double getArea();
}
在上面的代码中,我们定义了一个抽象类 Shape,它有一个非抽象方法 move() 和一个抽象方法 getArea()。getArea() 方法没有具体的实现体,因此需要等待该抽象类被继承之后再进行方法的实现,而非抽象方法 move() 则有具体的实现体,可以在该抽象类中直接调用。
总之,抽象方法是一种十分有用的语言特性,在设计大型程序时可以使用抽象方法来高效地管理和维护代码结构。
十一、不可变对象
不可变对象是指一旦在内存中创建后,它的状态(即成员变量的值)就不能够被改变且该对象的方法也不会改变其状态。换句话说,不可变对象是始终保持着每一个属性值不变的对象,而不管发生什么情况。
在 Java 中,不可变对象可以通过如下方式实现:
- 使用
final
关键字修饰类和字段:将一个类声明为 final 类型之后,就无法从该类继承子类;将一个属性定义为 final 类型之后,就不能再对该属性进行赋值了。(内容不能修改,没有修改方法) - 将所有的属性设置为私有的,并提供公开的 getter 方法来获取属性值,但禁止任何 setter 方法或具有副作用的方法:在不可变对象中,所声明的所有属性都应该是私有的,外部代码只能通过访问 getter 方法来获取属性值,而不能直接修改该属性的值。
- 如果需要进行修改,则返回一个新的对象:由于不可变对象的状态是不可变的,因此我们无法修改对象的状态。如果需要对不可变对象进行操作,那么只能够返回一个新的对象。
- 不可变对象的引用类型属性也必须是不可变的
以字符串类型为例,Java 中的字符串类型就是一个典型的不可变对象,一个字符串对象一旦被创建便不可更改,任何对字符串的操作都不会对该字符串本身造成影响,而是会产生一个新的字符串对象。
使用不可变对象可以保证对象状态的安全性,从而避免一些潜在的错误和并发问题。同时,由于不可变对象无法被修改,因此也具有更好的线程安全性。
十二、返回ArrayList x的第一个元素:x.get(0);返回最后一个元素
if(!x.isEmpty()) {
return x.get(x.size() - 1);
} else {
// handle empty list
}
这会检查列表是否为空,如果不是空的,就返回第x.size()-1个位置上的元素,即最后一个元素。如果列表为空,则需要处理所需的异常情况。
十三、打印
int count = 0;
do {
System.out.println("Welcome to Java");
} while (count++ < 10);
打印11次,在第一次执行循环体时 count 的值为 0,因为 count++ 是一个后置递增运算,所以 while 条件比较 count 和 10 后会得到 true。此时循环从头开始再次执行,输出 "Welcome to Java" 并将 count 的值加 1,使其变成了 1。
在第二次执行循环体时 count 的值已经是 1 了,再次判断 while 条件仍然为 true。以此类推,直到 count 的值变成了 10,循环体被执行了 10 次而这时再次判断 while 条件时,count 的值已经变成了 11 (前面执行了 10 次 ++ 操作),不满足条件,于是循环结束。总共会打印 "Welcome to Java" 11 次。
十四、最小公倍数
int x = 12,y = 15,k;
for(k = x; k<=x*y;k=k+x)
if(k % x==0 && k % y==0)
break;
K=60
具体的执行过程如下:
- 初始化变量 x = 12, y = 15, k;
- 进入循环: 将 k 的初始值设置为 x 即 k = x = 12;
- 每次循环进入,先检查 k 是否符合条件,如果符合条件(即能被 x 和 y 整除),则退出循环。
- 如果循环结束仍没有找到符合条件的 k 值,则 k = x * y,因为 x*y 是 x 和 y 的一个上界,但通常情况下会在很早的时候就找到了符合条件的 k 值而跳出循环。
根据步骤3中的条件表达式,可以看到在本次循环中 k 必须能同时被 x 和 y 整除。由于 k 的取值范围是从 x 开始,在每次循环后不断加上 x,因此,当 k 能被 x 和 y 整除时,一定有 k % x == 0 且 k % y == 0。因此,条件表达式可以用 "&&" 运算符连接两个条件判断,只有当两者均为真时才进行 break 跳出循环
十五、times、count
在这段代码中,times 变量的值为 0 是因为在 for 循环外部初始化时赋初始值为 0,并且在 for 循环内部没有对它的值进行修改。所以即使执行了 100 次循环,times 变量的值仍然是 0。
而 count 的值为 101 是因为increment 方法接收一个 Count 类型的参数和一个 int 类型的参数,其中 Count 对象会传递引用到 increment 方法中。当 increment 方法执行 c.count++ 语句时,实际上改变的是 Count 对象的 count 属性,因此每次调用 incremental 方法都会将 Count 对象的 count 值加 1。由于循环执行了 100 次,所以 Count 对象的 count 值最终会变成 101。
十六、为什么final修饰下的String可调用静态方法通用整个类
Final 修饰的类不能继承,final修饰的方法不能调用,final修饰的变量不能修改
final关键字在Java中用于表示最终不可变的,可以被应用于类、方法和变量三个级别。
对于final修饰的类, 是指这个类不能被其他类继承。final修饰类时意味着它不能被更改,因此它是一个不可变的类,一旦定义了就无法修改其属性。
对于final修饰的方法, 是指这个方法不能被子类所重写。当我们需要确保某个方法在整个程序中具有恒定的行为时,就可以使用 final 修饰符。final方法在Java中也是一种优化技术,可以避免被重载函数的虚拟机调用。
对于final修饰的变量, 是指这个变量只能被赋值一次,并且在之后的执行过程中不能再重新赋值。实际上,final 变量可以在声明时直接进行初始化,或在构造方法中赋值(但是 constructor 不会承认代码块赋值)。 final 修饰符可以确保该变量的值不会在程序运行期间被修改,增加了程序安全性。
总之,final关键字提供了很多机制来确保类不会被改变,方法不会被覆盖,变量不会被改变。这种约束非常有用,特别是在创建大型软件项目时,通过使用 final 关键字可以更好地定义整个程序的结构和实现。
public class Base {
public static final String FOO = "foo";
public static void main(String[] args) {
Base b = new Base();
Sub s = new Sub();
System.out.print(Base.FOO);
System.out.print(Sub.FOO);
System.out.print(b.FOO);
System.out.print(s.FOO);
System.out.print(((Base)s).FOO);} }
class Sub extends Base {public static final String FOO="bar";}
输出结果为?
在代码中,Base 类定义了一个名为 FOO 的常量并赋值为 "foo"。Sub 类继承了 Base 类,并覆盖了 FOO 常量的值为 "bar"。
在 main 方法中,首先创建了一个 Base 对象 b 和一个 Sub 对象 s。然后依次打印了 Base.FOO, Sub.FOO, b.FOO, s.FOO 和 ((Base)s).FOO。
对于第一次调用 System.out.print(Base.FOO),直接输出 Base 类中定义的 FOO 常量的值 "foo"。
对于第二次调用 System.out.print(Sub.FOO),Sub 类中覆盖了 FOO 常量的值为 "bar"。所以打印的是 "bar"。
对于第三次调用 System.out.print(b.FOO),b 是一个 Base 类型的对象,它直接使用由 Base 类定义的 FOO 常量的值 "foo"。
对于第四次调用 System.out.print(s.FOO),s 是一个 Sub 类型的对象,它直接使用由 Sub 类覆盖的 FOO 常量的值 "bar"。
对于最后一次调用 System.out.print(((Base)s).FOO),((Base)s) 会将 Sub 对象 s 向上转型为一个 Base 类型的对象,这个对象只能访问到 Base 类中定义的 FOO 常量的值 "foo"。
因此,在控制台上的输出结果为 "foofoobarfoofoo"。
在 Java 中,final 修饰符可以用来修饰类、方法和变量。不同的使用情况下,final 有一些不同的作用:
- 如果 final 修饰一个类,则表示该类不能被继承。
- 如果 final 修饰一个方法,则表示该方法不能被子类重写。
- 如果 final 修饰一个变量,则表示该变量只能被赋值一次。如果 final 变量是一个基本数据类型,则其数值一旦在初始化后便不能更改;如果是引用类型,则其引用地址一旦在初始化后便不能再指向其他对象。
对于问题中的代码,Base 类中定义了一个 final 的常量 FOO,并且 Sub 类中覆盖了此常量的值为 "bar"。虽然 FOO 被声明为 final,但子类仍然可以访问它。原因是,子类中的 FOO 常量与父类中的 FOO 常量并不是同一个变量,而是子类重新定义并覆盖了父类中的常量。
Java 允许在子类中使用与父类相同的名称来定义变量,但父类中的 final 常量只是代表这个常量在声明后不可再赋值其指向对象的对象,而并不意味着子类无法通过同名常量的方式来在自己内部建立新的引用。
因此,在子类中重新定义和使用 final 常量只是在子类自己的作用域内完成其初始化过程,并不影响父类的 final 常量
十七、
public class Test {
public static void main(String [] args) {
int x = 5;
boolean b1 = true;
boolean b2 = false;
if ((x == 4) && !b2 )
System.out.print("1 ");
System.out.print("2 "); 在if条件语句外面
if ((b2 = true) && b1 )
System.out.print("3 ");
}
If语句没有大括号,表示只有一行代码执行,“==”表示判断,“=”赋值
&&"和 "||" 都是逻辑运算符,用于判断条件的真假。它们通常用于if、while以及其他控制语句中,帮助决定程序流程。
"&&" 表示逻辑与(and)运算符,在两个条件都为 true 时返回 true,否则返回 false。例如,表达式 (a > 0) && (b > 0) 的值为 true 当且仅当变量 a 和 b 均大于 0 时才会返回 true,否则返回 false。
"||" 表示逻辑或(or)运算符,在两个条件中至少有一个为 true 时返回 true,只有两个条件都为 false 时才返回 false。例如,表达式 (a > 0) || (b > 0) 的值为 true 当且仅当变量a和b中有一个大于0时就会返回true。
在编写程序时,可以根据需要使用逻辑运算符来组合多个条件判断,从而实现更加复杂的代码逻辑。
- 在 main 方法中,定义并初始化整型变量 x 为 5,布尔类型变量 b1 和 b2 分别为 true 和 false。
- 执行第一个 if 语句判断 ((x == 4) && !b2 ) 的结果,由于 x 不等于 4 并且 b2 的值为 false 取反后为 true,所以判断结果为 false,if 语句不会执行。因此,输出 "2 "。
- 执行第二个 if 语句判断 ((b2 = true) && b1 ) 的结果,其中 b2 被赋值为 true,表达式的返回值为 true,并且 b1 的值也为 true,所以判断结果也为 true。因此,输出 "3"。
- 程序执行结束。最终在控制台输出的内容为 "2 3"。
总之,该程序会依次输出 "2" 和 "3" 这两个字符串。
十八、
Is-a:
是a:A Is B:A是B(继承关系,继承)。
has-a:
有a:A has B:A有B(从属关系,聚合)。
都是一个类的属性
like-a:
像a:A like B:A像B(组合关系,接口)。
关于Is-a、has-a、like-a的使用场景:
如果A,B是Is-a关系,那么应该使用继承,例:玻璃杯、塑料杯都是杯子。
如果A,B是has-a关系,那么应该是用聚合,例:汽车由发动机,底盘,车身,电气设备等组成,那么应该把发动机,底盘这些类聚合成汽车。
如果A,B是like-a关系,那么应该使用组合,例:空调继承于制冷机,但它同时有加热功能,那么你应该把让空调继承制冷机类,并实现加热接口。
十九、
import java.io.*;
public class Maker {
public static void main(String[] args) {
File dir = new File("dir");
File f = new File(dir, "f"); }
}
为什么在文件中没有增加任何东西
在给定的程序中,并没有文件增加操作的代码。第4~6行代码创建了一个 File 类型的对象 dir 代表名为 "dir" 的目录,以及一个 File 类型的对象 f 代表 dir 目录下名为 "f" 的文件。
即使这些文件或目录不存在,使用 File 类型的对象也不会自动创建它们。要向文件中写入数据,需要执行相应的写入操作,例如使用 IO 流来打开文件并将内容写入文件中
二十、
public static void main(String[] args) {
new A() ;
new B();}
}
class A {
int i=7;
public A() {
setI(20);
System.out.println("A1 from B is"+i);
}
public void setI(int i){
this.i =2*i;}
}
class B extends A {
public B() {
System.out.println("B2 from B is"+i);
}
public void setI(int i){
this.i =3*i;}
}
是因为在 main 方法中,先实例化了一个 A 类对象和一个 B 类对象。
A 类有一个 int 类型的成员变量 i,它的初始值为 7。当实例化 A 类时,会调用 A 类的构造方法,并将 i 的值设置为 20。之后再调用 setI(40) 方法将 i 的值修改为 40。
当实例化 B 类时,会先调用 A 类的构造方法,而在构造方法中会将 i 的值设置为 20。之后再调用 setI(60) 方法将 i 的值修改为 60。
最终打印出的结果是 A1 from B is40、A1 from B is60 和 B2 from B is60。其中,第一行和第二行都是从 B 对象获取 i 的值并转换成字符串拼接得到的,但第一行的 i 值还未被修改,所以是 40;第二行的 i 值已经被修改为 60。
第三行的结果是从 B2 对象获取 i 的值并转换成字符串拼接得到的,由于 B2 对象已经调用了 setI(60),所以 i 的值为 60。
二一、
public class Test3{
public static void main(String[]args){
new Person(). printPerson();
new Student(). printPerson();}}
class Student extends Person {private String getInfo(){return "Student";}}
class Person {private String getInfo(){return "Person";}
public void printPerson(){System. out. println(getInfo());}}
- Student 类的 getInfo() 方法被声明为了 private,这样在 Person 类中无法访问该方法。因此,在 Person 类的 printPerson() 方法中调用的仍然是 Person 类的 getInfo() 方法,而不是 Student 类的 getInfo() 方法。
- Student 类并没有重写 printPerson() 方法,因此,当实例化 Student 对象并调用其 printPerson() 方法时,也会输出 "Person"。
因此,无论是实例化 Person 对象还是 Student 对象,都会调用 Person 类的 printPerson() 方法,并在控制台上输出 "Person" 字符串,因此最终输出结果为 "PersonPerson"。
如果想要 Student 类的 getInfo() 方法能够被 Person 类正确访问,可以将该方法的访问权限修改为 protected 或 public。同时,如果想要重写 Person 类的方法,需要使用 @Override 注解,确保方法签名与父类一致
覆盖
在面向对象编程中,方法的覆盖(override)是指子类重新定义了父类已有的方法,使得父类的该方法对子类不再有效,而在子类实例中调用该方法时会执行子类所定义的方法。
方法覆盖发生的前提是子类与父类拥有相同的方法签名(包括方法名、参数列表和返回类型),即子类的方法名、参数列表和返回类型都与父类的方法相同。此外,子类不能缩小父类方法的访问权限(如从 public 缩小到 protected),但可以扩大其访问权限(如从 protected 扩大到 public)。
当在子类中重写某个父类方法时,需要使用 @Override
注解来标记该方法,这将告诉编译器该方法是一个子类方法,需要覆盖父类中已存在的同名方法。
在程序运行时,如果创建了子类的实例并调用该方法,则子类中覆盖后的方法将被执行,否则父类中的原始方法将被执行。因此,在设计继承关系时应当合理地使用方法覆盖,以确保正确的程序行为和良好的可维护性。
强制转化
((A)new B()).start()
假设 A 和 B 是两个类,其中 B 是 A 的子类,且 A 和 B 都拥有继承自 Thread 类的 start() 方法。执行 ((A)new B()).start() 会发生以下操作:
- 创建一个 B 的新实例。
- 使用强制类型转换将该实例转换为其父类 A 的类型。但是由于 B 是 A 的子类,因此可以安全地将一个 B 类型的对象强制转换为 A 类型。
- 调用实际上是 B 对象所属的类中定义的 start() 方法(而不是 A 中定义的那个方法),这是因为在运行时 Java 动态绑定该方法,而不是在编译时静态绑定。
总之,该语句将创建一个 B 类型的新实例,并且在该实例的 start() 方法上调用线程启动,开始执行该方法的代码块,在线程中异步并行执行任务。
如何区分何时父类引用父类的对象,何时父类引用子类类型对象
在面向对象编程中,当使用父类类型的引用来引用父类或子类对象时,需要注意以下两种情况:
- 父类引用父类对象:如果使用父类类型的引用变量来引用一个父类类型的实例,则只能使用父类中定义的方法和属性。因为该引用变量仅仅是可以指向一个父类类型的对象,而无法获得该对象更具体的类型信息。
例如,假设有一个 Animal 类及其子类 Dog 和 Cat,它们都有一个 eat() 方法。如果将 Animal 类型的变量声明为 a,并将其指向 Animal 类型的一个实例,则 a.eat() 方法将调用 Animal 类中的 eat() 方法。
Animal a = new Animal(); // 父类引用父类对象
a.eat(); // 调用 Animal 中的 eat() 方法
- 父类引用子类对象:如果使用父类类型的引用变量来引用一个子类类型的实例,则可以使用父类和子类中定义的方法和属性,但是如果子类中重写了父类中的某个方法,则实际上会调用子类中的方法。
例如,如果我们将上面例子中的 a 变量赋值为 Dog 类型的实例,则 a.eat() 方法将调用 Dog 类中的 eat() 方法。
Animal a = new Dog(); // 父类引用子类对象
a.eat(); // 调用 Dog 中的 eat() 方法
总之,父类引用父类对象时只能调用父类中定义的方法和属性,而父类引用子类对象时可以调用父类和子类中定义的方法和属性(但如果子类中定义了与父类同名的方法,则优先调用子类中的定义)。
缓冲流有什么作用,基本语句
继承抽象类的子类必须实现(覆盖)父类中的所有抽象方法,且子类中实现的方法的签名必须与父类中抽象方法的签名完全一致。在实现子类中的抽象方法时,可以更改方法的访问修饰符,但是不能改变返回值类型和方法名。
因此,在继承抽象类时,如果要改变抽象方法的数据类型,必须先确定新数据类型与原数据类型存在继承或兼容关系,然后再进行数据类型转换。否则,在实现子类的抽象方法时,将无法满足原抽象方法的签名定义,从而导致编译错误。
下面是一个示例,演示了如何在继承抽象类时更改参数和返回值类型的数据类型:
abstract class Shape {
public abstract double area();
}
class Circle extends Shape { // 子类继承 Shape 抽象类
private final double radius; // 半径
public Circle(double radius) { // 构造函数
this.radius = radius;
}
@Override
public double area() { // 实现 Shape 中的 area 方法
return Math.PI * radius * radius;
}
}
class Square extends Shape { // 子类继承 Shape 抽象类
private final double sideLength; // 边长
public Square(double sideLength) { // 构造函数
this.sideLength = sideLength;
}
@Override
public double area() { // 实现 Shape 中的 area 方法
return sideLength * sideLength;
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Shape shape1 = new Circle(5.0); // 使用子类创建对象,多态性
System.out.println("圆形面积:" + shape1.area());
Shape shape2 = new Square(3.0); // 使用子类创建对象,多态性
System.out.println("正方形面积:" + shape2.area());
}
}
在这个示例中,Shape
是一个抽象类,其中包含了一个 area()
抽象方法。Circle
和 Square
类都是 Shape
类的子类,在继承 Shape
类后必须实现其中的所有抽象方法。
在子类中,分别实现了 area()
方法,并根据自己不同的数据类型进行计算。使用多态性创建两个不同类型的对象(一个是圆,一个是正方形),并且通过各自对象调用 area()
方法来获取它们各自的面积。在这里,虽然修改了具体数据类型,但仍保持了数据类型的兼容性和一致性,从而遵循了方法签名的规则。