目录
1、重载和重写区别?
2、构造器(Constructor)是否可被重写(override)
3、break 、continue 、return 作用?
4、JAVA 创建对象有哪些方式?
5、== 和 equals 有什么区别?
6、Integer 和 int 有什么区别?
1、重载和重写什么区别?
定义与范围:
- 重载:在同一个类中,可以有多个同名但参数列表不同的方法。这样的方法被称为重载方法。重载是编译时的多态性的一种表现,它允许类以统一的方式处理不同类型的数据。
- 重写:在面向对象编程中,子类可以重新定义父类中已有的方法,这被称为重写。重写是子类与父类之间的一种关系,允许子类根据自身的需求修改父类中的方法实现。
参数与多态性:
- 重载:重载方法的参数个数、参数类型或参数的顺序可以不同。返回值类型可以相同,也可以不相同。重载的参数列表必须不同,否则编译器会报错。
- 重写:子类重写父类的方法时,方法名、参数列表和返回类型必须与父类中被重写的方法相同。重写是运行时的多态性的一种表现,子类对象在调用方法时会根据实际类型来确定调用哪个版本的方法。
修饰符与访问权限:
- 重载:重载方法对修饰符和访问权限没有特定要求。
- 重写:重写方法的访问权限不能低于被重写方法的访问权限。例如,如果父类中的方法是public的,那么子类中的重写方法也必须是public的。此外,重写方法的异常处理也不能比被重写方法的异常处理更严格。
功能与目的:
- 重载:通过提供多个具有不同参数列表的同名方法,可以使类更加灵活和易于使用。重载方法可以根据传入的参数类型或数量来执行不同的操作。
- 重写:通过子类重写父类的方法,可以实现对父类功能的修改或扩展。重写方法允许子类根据自身的需求重新定义父类中的方法实现,从而实现多态性。这有助于在面向对象编程中实现代码的复用和扩展性。
举例:
- 重载(Overloading):在同一类中定义多个同名但参数列表不同的方法
public class OverloadExample { // 第一个版本的print方法,没有参数 public void print() { System.out.println("No parameters"); } // 第二个版本的print方法,接受一个整数参数 public void print(int i) { System.out.println("Integer parameter: " + i); } // 第三个版本的print方法,接受一个字符串参数 public void print(String s) { System.out.println("String parameter: " + s); } public static void main(String[] args) { OverloadExample example = new OverloadExample(); example.print(); // 调用无参数的print方法 example.print(10); // 调用接受整数参数的print方法 example.print("Hello"); // 调用接受字符串参数的print方法 }
}
- 重写(Overriding):
子类可以重写父类的方法。当子类对象调用这个方法时,会执行子类中的实现:
class Animal { // 父类中的方法 public void makeSound() { System.out.println("The animal makes a sound"); }
} class Dog extends Animal { // 子类重写父类中的方法 @Override // 这个注解是可选的,但建议使用以明确表示我们正在重写父类的方法 public void makeSound() { System.out.println("The dog barks"); }
} public class OverridingExample { public static void main(String[] args) { Animal myAnimal = new Animal(); myAnimal.makeSound(); // 输出 "The animal makes a sound" Dog myDog = new Dog(); myDog.makeSound(); // 输出 "The dog barks" // 向上转型,但仍然调用子类的重写方法 Animal myPet = new Dog(); myPet.makeSound(); // 输出 "The dog barks",因为实际对象是Dog类型 }
}
例子中,Dog类继承了Animal类,并重写了makeSound方法。当创建Dog对象并调用makeSound方法时,会执行Dog类中的实现。同时,由于Java支持向上转型(将子类对象赋值给父类引用),所以即使将Dog对象赋值给Animal类型的引用,调用makeSound方法时仍然会执行Dog类中的实现。
2、构造器(Constructor)是否可被重写(override)
不能。这是因为构造器不是类的方法,而是用于初始化对象的状态的特殊方法。在Java中,重写(overriding)是指子类提供一个与父类方法签名完全相同的方法的行为,但构造器的签名包括类名,这是唯一的,因此子类不能有一个与父类完全相同的构造器签名。
然而,子类可以定义自己的构造器,这些构造器通常会调用父类的构造器(使用super
关键字)来确保父类中的初始化代码被执行。如果子类没有显式地调用父类的构造器,编译器会默认调用父类的无参构造器(如果父类中存在的话)。
子类构造器如何调用父类构造器的例子:
class Parent { public Parent() { System.out.println("Parent's constructor called"); } public Parent(String message) { System.out.println("Parent's constructor with message called: " + message); }
} class Child extends Parent { public Child() { // 隐式调用父类的无参构造器 super(); // 可以显式调用,但在这个例子中是多余的 System.out.println("Child's constructor called"); } public Child(String message) { // 显式调用父类带有一个字符串参数的构造器 super(message); System.out.println("Child's constructor with message called"); }
} public class ConstructorExample { public static void main(String[] args) { Child child1 = new Child(); // 输出 Parent's constructor called 和 Child's constructor called Child child2 = new Child("Hello"); // 输出 Parent's constructor with message called: Hello 和 Child's constructor with message called }
}
在这个例子中,Child
类有两个构造器,一个无参构造器和一个带有一个字符串参数的构造器。这两个构造器都显式或隐式地调用了Parent
类的构造器来确保父类状态被正确初始化。但是,这并不是重写构造器,而是子类构造器调用父类构造器的一种机制。
3、break 、continue 、return 作用?
都是用于控制程序流程的关键字,但它们在用途和上下文中有所不同。break用于终止循环或switch语句,continue 用于跳过当前循环迭代,而return用于从方法中返回一个值并退出方法。
-
break:
break语句用于立即终止最内层循环(for、while、do-while)或 switch语句的执行。当break语句被执行时,程序会跳出当前的循环或switch语句,继续执行循环或switch语句后面的代码(如有)
for (int i = 0; i < 10; i++) { if (i == 5) { break; // 当 i 等于 5 时,跳出循环 } System.out.println(i); // 这将打印 0 到 4
}
- continue:
continue语句用于跳过当前循环迭代中的剩余代码,并立即开始下一次迭代。它不会终止整个循环,而是只跳过当前迭代中的剩余代码。
for (int i = 0; i < 10; i++) { if (i == 5) { continue; // 当 i 等于 5 时,跳过当前迭代 } System.out.println(i); // 这将打印 0 到 4 和 6 到 9,但不包括 5
}
-
return:
return语句用于从方法中返回一个值,并立即结束该方法的执行。如果方法具有返回值类型(即不是void),则return语句必须返回一个与声明类型兼容的值。如果方法没有返回值(即返回类型为void),则return语句可以用于无条件地退出方法。
public int getMax(int a, int b) { if (a > b) { return a; // 返回较大的数并退出方法 } else { return b; // 返回较小的数并退出方法 }
} public void printGreeting() { System.out.println("Hello!"); return; // 退出方法,但因为没有返回值类型,所以不需要返回任何值
}
4、JAVA 创建对象有哪些方式?
在Java中,创建对象(或称为实例化对象)的主要方式有以下几种:
-
使用
new
关键字:这是最常见的方式,通过
new
关键字调用类的构造函数来创建一个新的对象。MyClass obj = new MyClass();
-
使用反射(Reflection):通过
Class
对象的newInstance()
方法或Constructor
对象的newInstance()
方法,可以在运行时动态地创建对象。这通常用于框架和插件系统等,因为可以在运行时才确定要实例化的类。注意:从Java 9开始,newInstance()
方法已经被标记为废弃,应使用getDeclaredConstructor().newInstance()
。Class<?> clazz = MyClass.class; MyClass obj = (MyClass) clazz.newInstance(); // 需要无参构造函数 // 或者使用Constructor Constructor<?> constructor = clazz.getConstructor(); MyClass obj2 = (MyClass) constructor.newInstance();
-
使用克隆(Cloning):
如果类实现了
Cloneable
接口并覆盖了clone()
方法,就可以通过调用clone()
方法来创建一个对象的副本。MyClass original = new MyClass(); MyClass clone = (MyClass) original.clone();
注意:
clone()
方法默认是保护方法(protected
),因此需要在子类中才能访问,或者通过类的公有方法提供访问。 -
使用序列化(Serialization)和反序列化(Deserialization):
如果类实现了
Serializable
接口,那么就可以将对象序列化为字节流,然后再从字节流中反序列化出一个新的对象。这通常用于持久化对象或在网络中传输对象。ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(original); // 序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); MyClass deserialized = (MyClass) ois.readObject(); // 反序列化
-
使用工厂模式(Factory Pattern):
工厂模式是一种创建型设计模式,它封装了对象的创建过程,使用工厂方法代替
new
操作符来创建对象。这可以隐藏对象的创建逻辑,使得代码更加灵活和可维护。public class MyClassFactory { public static MyClass createInstance() { return new MyClass(); } } // 使用 MyClass obj = MyClassFactory.createInstance();
-
使用建造者模式(Builder Pattern):
建造者模式用于创建复杂对象,允许在创建对象的过程中设置多个参数。建造者模式通常包含一个建造者类和一个产品类。
MyClass.Builder builder = new MyClass.Builder(); MyClass obj = builder.withParam1("value1").withParam2(42).build();
-
使用单例模式(Singleton Pattern):
虽然单例模式不是直接创建对象的方式,但它确保了一个类只有一个实例,并提供了一个全局访问点。在某些情况下,这可能被视为一种创建对象的方式。
MyClass obj = MyClass.getInstance();
5、== 和 equals 有什么区别?
在Java中,都用于比较两个对象或值,但它们的用法和上下文存在显著差异
-
基本数据类型与对象类型:
- 对于基本数据类型(
byte、
short、
int
、long、
boolean、char、
float、
double
),==
用于比较它们的值是否相等。 - 对于对象类型(即类的实例),
==
默认用于比较两个对象的引用是否指向内存中的同一个对象(即比较的是对象的内存地址)。
- 对于基本数据类型(
-
equals()
方法:equals()
方法是java.lang.Object
类中的一个方法,用于比较两个对象的内容(或称为“值相等性”)是否相等。- 在
Object
类中,equals()
方法的默认实现与==
相同,即比较对象的引用是否相等。 - 但是,许多类(如
String
,Integer
,Date
等)都重写了equals()
方法,以便比较对象的内容是否相等。 - 例如,对于
String
类型,str1.equals(str2)
将比较两个字符串的内容是否相同,而不是比较它们的引用是否相同。
6、Integer 和 int 有什么区别?
- 数据类型:
int
是 Java 的一种基本数据类型(primitive data type),它用于存储 32 位有符号的二进制整数。Integer
是 Java 提供的一个对int
类型的封装类(wrapper class),位于java.lang
包中。它提供了许多方法来操作int
类型的值,并且允许int
值被当作对象来处理(例如,在集合中使用)。 - 默认值:
int
类型的变量在声明时如果没有初始化,其默认值为 0。Integer
类型的对象在声明时如果没有初始化,其默认值为null
(因为Integer
是对象类型)。 - 内存占用:
int
类型的变量直接存储整数值,通常存储在栈内存中(如果作为局部变量)或对象的实例字段中(如果作为对象的属性)。Integer
对象在堆内存中分配内存,并且包含对整数值的引用(以及可能的额外开销,如对象头信息)。 - 自动装箱和拆箱:Java 5 引入了自动装箱(autoboxing)和拆箱(unboxing)的概念,允许在基本数据类型和对应的封装类之间自动转换。例如,
Integer i = 10;
会自动将int
类型的字面量10
装箱为Integer
对象。同样地,int j = i;
会自动将Integer
对象i
拆箱为int
类型的值。 - 缓存:对于
-128
到127
(包括两端)之间的Integer
对象,Java 使用了缓存机制,即当创建这些范围内的Integer
对象时,实际上返回的是对同一个对象的引用。这可以通过Integer.valueOf(int i)
方法来看到,该方法对于上述范围内的值会返回缓存中的对象,而对于其他值则会创建新的对象。 - 可空性:由于
int
是基本数据类型,它不能是null
。但是,Integer
作为对象类型,可以是null
。 - 方法:
Integer
类提供了许多有用的方法,如parseInt(String s)
用于将字符串转换为整数,compareTo(Integer anotherInteger)
用于比较两个Integer
对象的值等。而int
类型则没有这些方法。 - 性能:在大多数情况下,使用基本数据类型
int
比使用Integer
对象更高效,因为int
类型的变量不需要额外的内存开销和对象管理开销。然而,在需要将整数作为对象处理(如在集合中使用)的场景下,使用Integer
是必要的。
在性能敏感的场景下,优先考虑使用 int
;在需要将整数作为对象处理的场景下,使用Integer
。