类与 Object 的应用
1. 类的组成部分有哪些?
答:在 Java 语言中,类主要是由方法和变量两部分组成。
2. 类与对象有哪些区别?
答:类是一个抽象的概念,是对某一事物的描述;而对象是类的实例,是实实在在存在的个体。比如,“人”就是一个类(一个概念),而老王(王磊)就是实实在在的一个“对象”。
3. Java 中可以多继承吗?
答:Java 中只能单继承,但可以实现多接口。
4. Java 中为什么不能实现多继承?
答:从技术的实现角度来说,是为了降低编程的复杂性。假设 A 类中有一个 m() 方法,B 类中也有一个 m() 方法,如果 C 类同时继承 A 类和 B
类,那调用 C 类的 m() 方法时就会产生歧义,这无疑增加了程序开发的复杂性,为了避免这种问题的产生,Java 语言规定不能多继承类,但可以实现多接口。
5. 覆盖和重载有哪些区别?
答:覆盖和重载的区别如下:
- 覆盖(Override)是指子类对父类方法的一种重写,只能比父类抛出更少的异常,访问权限不能比父类的小,被覆盖的方法不能是 private,否则只是在子类中重新定义了一个方法;
- 重载(Overload)表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同。
6. 以下不属于重载特性的是?
A:方法的参数类型不同
B:方法的返回值不同
C:方法的参数个数不同
D:方法的参数顺序不同
答:B
7. 为什么方法不能根据返回类型来区分重载?
答:因为在方法调用时,如果不指定类型信息,编译器就不知道你要调用哪个方法了。比如,以下代码:
float max(int x,int y);
int max(int x,int y);
// 方法调用
max(1,2);
因为 max(1,2)
没有指定返回值,编译器就不知道要调用哪个方法了。
8. 构造方法有哪些特征?
答:构造方法的特征如下:
- 构造方法必须与类名相同;
- 构造方法没有返回类型(void 也不能有);
- 构造方法不能被继承、覆盖、直接调用;
- 类定义时提供了默认的无参构造方法;
- 构造方法可以私有,外部无法使用私有构造方法创建对象。
9. 构造函数能不能被覆盖?能不能被重载?
答:构造函数可以重载,但不能覆盖。
10. 以下说法正确的是?
A:类中的构造方法不能忽略
B:构造方法可以作为普通方法被调用
C:构造方法在对象被 new 时被调用
D:一个类只能有一个构造方法
答:C
11. 以下程序执行的结果是?
class ExecTest {
public static void main(String[] args) {
Son son = new Son();
}
}
class Parent{
{
System.out.print("1");
}
static{
System.out.print("2");
}
public Parent(){
System.out.print("3");
}
}
class Son extends Parent{
{
System.out.print("4");
}
static{
System.out.print("5");
}
public Son(){
System.out.print("6");
}
}
答:打印的结果是:251346
加载顺序如下:
- 执行父类的静态成员;
- 执行子类的静态成员;
- 父类的实例成员和实例初始化;
- 执行父类构造方法;
- 子类的实例成员和实例初始化;
- 子类构造方法。
12. 以下程序执行的结果是?
class A {
public int x = 0;
public static int y = 0;
public void m() {
System.out.print("A");
}
}
class B extends A {
public int x = 1;
public static int y = 2;
public void m() {
System.out.print("B");
}
public static void main(String[] args) {
A myClass = new B();
System.out.print(myClass.x);
System.out.print(myClass.y);
myClass.m();
}
}
答:打印的结果是:00B
题目解析:在 Java 语言中,变量不能被重写。
13. 以下程序执行的结果是?
class A {
public void m(A a) {
System.out.println("AA");
}
public void m(D d) {
System.out.println("AD");
}
}
class B extends A {
@Override
public void m(A a) {
System.out.println("BA");
}
public void m(B b) {
System.out.println("BD");
}
public static void main(String[] args) {
A a = new B();
B b = new B();
C c = new C();
D d = new D();
a.m(a);
a.m(b);
a.m(c);
a.m(d);
}
}
class C extends B{}
class D extends B{}
答:打印结果如下。
BA
BA
BA
AD
题目解析:
- 第一个 BA:因为 A 的 m() 方法,被子类 B 重写了,所以输出是:BA;
- 第二个 BA:因为 B 是 A 的子类,当调用父类 m() 方法时,发现 m() 方法被 B 类重写了,所以会调用 B 中的 m() 方法,输出就是:BA;
- 第三个 BA:因为 C 是 B 的子类,会直接调用 B 的 m() 方法,所以输出就是:BA;
- 第四个 AD:因为 D 是 A 的子类,所以会调用 A 的 m() 方法,所以输出就是:AD。
14. Java 中的 this 和 super 有哪些区别?
答:this 和 super 都是 Java 中的关键字,起指代作用,在构造方法中必须出现在第一行,它们的区别如下。
- 基础概念:this 是访问本类实例属性或方法;super 是子类访问父类中的属性或方法。
- 查找范围:this 先查本类,没有的话再查父类;super 直接访问父类。
- 使用:this 单独使用时,表示当前对象;super 在子类覆盖父类方法时,访问父类同名方法。
15. 在静态方法中可以使用 this 或 super 吗?为什么?
答:在静态方法中不能使用 this 或 super,因为 this 和 super
指代的都是需要被创建出来的对象,而静态方法在类加载的时候就已经创建了,所以没办法在静态方法中使用 this 或 super。
16. 静态方法的使用需要注意哪些问题?
答:静态方法的使用需要注意以下两个问题:
- 静态方法中不能使用实例成员变量和实例方法;
- 静态方法中不能使用 this 和 super。
17. final 修饰符的作用有哪些?
答:final 修饰符作用如下:
- 被 final 修饰的类不能被继承;
- 被 final 修饰的方法不能被重写;
- 被 final 修饰的变量不能被修改。
18. 覆盖 equals() 方法的时候需要遵守哪些规则?
答:Oracle 官方的文档对于 equals() 重写制定的规则如下。
- 自反性:对于任意非空的引用值 x,x.equals(x) 返回值为真。
- 对称性:对于任意非空的引用值 x 和 y,x.equals(y) 必须和 y.equals(x) 返回相同的结果。
- 传递性:对于任意的非空引用值 x、y 和 z,如果 x.equals(y) 返回值为真,y.equals(z) 返回值也为真,那么 x.equals(z) 也必须返回值为真。
- 一致性:对于任意非空的引用值 x 和 y,无论调用 x.equals(y) 多少次,都要返回相同的结果。在比较的过程中,对象中的数据不能被修改。
- 对于任意的非空引用值 x,x.equals(null) 必须返回假。
此题目不要求记忆,能知道大概即可,属于加分项题目。
19. 在 Object 中 notify() 和 notifyAll() 方法有什么区别?
答:notify() 方法随机唤醒一个等待的线程,而 notifyAll() 方法将唤醒所有在等待的线程。
20. 如何使用 clone() 方法?
答:如果是同一个类中使用的话,只需要实现 Cloneable 接口,定义或者处理 CloneNotSupportedException
异常即可,请参考以下代码:
class CloneTest implements Cloneable {
int num;
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest ct = new CloneTest();
ct.num = 666;
System.out.println(ct.num);
CloneTest ct2 = (CloneTest) ct.clone();
System.out.println(ct2.num);
}
}
如果非内部类调用 clone() 的话,需要重写 clone() 方法,请参考以下代码:
class CloneTest implements Cloneable {
int num;
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest ct = new CloneTest();
ct.num = 666;
System.out.println(ct.num);
CloneTest ct2 = (CloneTest) ct.clone();
System.out.println(ct2.num);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneTest2 {
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest ct = new CloneTest();
ct.num = 666;
System.out.println(ct.num);
CloneTest ct2 = (CloneTest) ct.clone();
System.out.println(ct2.num);
}
}
总结
本文我们学习了类的基础用法,类引用:import 和 import static,访问修饰符的作用,构造函数和继承的特点以及使用技巧等,通过这些内容让我们对整个 Java 程序的组成,有了更加清晰直观的印象。
各种内部类和枚举类的使用
1.Java 中的内部类有哪些?
答:内部类包含以下 4 种:
- 静态内部类:static class StaticInnerClass{};
- 成员内部类:private class InstanceInnerClass{};
- 局部内部类:定义在方法或者表达式内部;
- 匿名内部类:(new Thread(){}).start()。
2.以下关于匿名内部类说法错误的是?
A:匿名内部类必须继承一个父类或者实现一个接口
B:匿名内部类中的方法不能是抽象的
C:匿名内部类可以实现接口的部分抽象方法
D:匿名内部类不能定义任何静态成员和方法
答:C
题目解析:匿名内部类规定必须实现接口的所有抽象方法,否则程序会报错,如下图所示。
3.以下枚举类比较“==”和“equals”结果一致吗?为什么?
class EnumTest {
public static void main(String[] args) {
ColorEnum redColor = ColorEnum.RED;
ColorEnum redColor2 = ColorEnum.RED;
System.out.println(redColor == redColor2);
System.out.println(redColor.equals(redColor2));
}
}
enum ColorEnum {
RED,
BLUE
}
答:结果一致,都是 true
。
题目分析:因为枚举类重写了 equals 方法,equals 方法里直接使用的 ==
比较的,而枚举类不能通过 new 进行创建,使用
ColorEnum.RED 得到的对象,其实使用的是对象的引用地址,所以 ==
比较的结果一定是 true。equals 被重写的源码如下图:
4.使用静态内部类的好处有哪些?
答:使用静态内部类的好处如下:
作用域不会扩散到包外;
- 可以通过“外部类.内部类”的方式直接访问;
- 内部类可以访问外部类中的所有静态属性和方法。
5.以下代码执行的结果是?
class OuterClass {
String name = "OuterClass";
protected static class InnerClass {
String name = "InnerClass";
public void sayHi() {
System.out.println(OuterClass.this.name);
}
}
}
class InnerClassTest {
public static void main(String[] args) {
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
innerClass.sayHi();
}
}
答:程序报错。
题目解析:在静态成员内部类中不能直接访问非静态外部类,因此程序会报错。
6.成员内部类和局部内部类有什么区别?
答:内部成员类和局部内部类的区别如下。
- 内部成员类可以使用任意访问修饰符,局部内部类不能使用任何访问修饰符;
- 局部内部类是声明在外部类的方法或其他作用域范围内的,内部类是直接声明在外部类之中的,与方法和属性平级。
7.为什么要使用内部类?内部类的使用场景有哪些?
答:使用内部类的好处有以下两个。
- 可以作为多继承的一种实现方式,最早内部类的实现就是平衡 Java 语言中没有多继承的一种方式;
- 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
内部类可以作为多继承的一种实现方式进行使用,因为每个内部类都能独立的继承一个类或接口,所以整个类就可以实现多继承。
8.以下代码执行的结果是?
class Outer {
public int num = 1;
class Inner {
public int num = 2;
public void show() {
int num = 3;
System.out.println(num);
System.out.println(this.num);
System.out.println(Outer.this.num);
}
}
}
class InnerTest {
public static void main(String[] args) {
new Outer().new Inner().show();
}
}
答:输出内容如下。
3
2
1
9.枚举有哪些应用场景?
答:枚举类的主要应用场景如下:
① 枚举类可作为高级的常量类
示例代码如下:
public enum Color {
RED("#FF0000", "255,0,0"),
GREEN("#00FFFF", "0,255,255"),
YELLOW("#FFFF00", "255,255,0");
String hex, rgb;
Color(String hex, String rgb) {
this.hex = hex;
this.rgb = rgb;
}
}
② 枚举类可方便的用于 switch 判断
示例代码如下:
switch(color)
{
case RED:
System.out.println("红灯停");
break;
case GREEN:
System.out.println("绿灯行");
break;
case YELLOW:
System.out.println("看情况");
break;
default:
System.out.println("灯坏了");
}
10.枚举类在 JVM 中是如何实现的?
答:枚举类在 JVM(Java 虚拟机) 中其实是通过普通的 static final 形式实现的。
题目解析:我们使用 javap 命令来分析枚举类最终编译的结果,查看编译后的结果,就找到了枚举类在 JVM 中的具体实现了。
首先定义一个枚举类,代码如下:
enum DBEnum {
ORACLE,
DB2,
MYSQL,
SQLSERVER
}
再使用命令 javac DBEnum.java
编译 .class 文件,然后再使用命令 javap DBEnum.class
,我们看到最终执行的结果如下:
Compiled from "EnumTest.java"
final class DBEnum extends java.lang.Enum<DBEnum> {
public static final DBEnum ORACLE;
public static final DBEnum DB2;
public static final DBEnum MYSQL;
public static final DBEnum SQLSERVER;
public static DBEnum[] values();
public static DBEnum valueOf(java.lang.String);
static {};
}
由此可以断定,枚举类在 JVM 中的实现也是通过普通的 static final 实现的。
11.枚举类可以被继承吗?
答:不能被继承,因为枚举类编译后的实际代码是 final class 的形式,类被 final 修饰了自然不能被继承。
12.枚举类是否是线程安全的?
答:枚举类是线程安全的,因为枚举类被编译后是 final class 的形式存在的,所以枚举类是线程安全的。
13.枚举是否可以被序列化?
答:枚举是可以被序列化的,Oracle 官方对此给出了说明,内容如下:
Enum constants are serialized differently than ordinary serializable or
externalizable objects. The serialized form of an enum constant consists
solely of its name; field values of the constant are not transmitted. To
serialize an enum constant, ObjectOutputStream writes the string returned by
the constant's name method. Like other serializable or externalizable objects,
enum constants can function as the targets of back references appearing
subsequently in the serialization stream. The process by which enum constants
are serialized cannot be customized; any class-specific writeObject and
writeReplace methods defined by enum types are ignored during serialization.
Similarly, any serialPersistentFields or serialVersionUID field declarations
are also ignored--all enum types have a fixed serialVersionUID of 0L
原文地址:https://docs.oracle.com/javase/8/docs/api/java/io/ObjectOutputStream.html
大致的意思是说:枚举的序列化和其他普通类的序列化不同,枚举序列化的时候,只是将枚举对象的 name 属性输出到结果中,反序列化的时候则是通过
java.lang.Enum 的 valueOf 方法根据名字查找枚举对象。
总结
通过本文我们系统地学习了 Java
的各种内部类:静态内部类、成员内部类、局部内部类、匿名内部类,知道了它们特点和区别,并学习了枚举类了使用,知道了枚举类在编译之后,其实还是普通的最终类(finalclass)。