Nowcoder Java专项练习

1.String字符串

1.1String的不可变性

以下代码执行后输出结果为( )

public class ClassTest{
     String str = new String("hello");
     char[] ch = {'a','b','c'};
     public void fun(String str, char ch[]){
     str="world";
     ch[0]='d';
 }
 public static void main(String[] args) {
     ClassTest test1 = new ClassTest();
     test1.fun(test1.str,test1.ch);
     System.out.print(test1.str + " and ");
     System.out.print(test1.ch);
     }
 }

答案:hello and dbc

解析:

String类型和数组都为引用数据类型。在main函数中调用fun方法传入的是str和ch的堆内存地址。

但是对于str,String类型被final修饰,不能够被继承,也就是值不能够被修改。String str = new String("hello");这段代码创建了两个对象,一个是在堆空间创建了一个String类型的str变量,另一个是在堆空间的字符串常量池中添加“hello”字符串。

在fun方法中,将“world”赋值给形参str,只是在堆内存中开辟了一个新的内存地址,str指向了新的内存地址,但是成员属性str指向堆内存的地址并没有改变,所以还是“hello”。

而ch[0]指向的是堆内存中对应地址的对象的内容,所以值发生改变。

2.子父类问题

2.1子父类和权限修饰符

以下代码的输出的是()

public class Person{
	private String name = "Person";
	int age=0;
}
public class Child extends Person{
	public String grade;
	public static void main(String[] args){
		Person p = new Child();
		System.out.println(p.name);
	}
}

答案:编译出错

解析:父类private的成员变量,根据权限修饰符的访问控制范围,只有在类内部才能被访问,就算是他的子类,也不能访问。

这里如果将Person p = new Child();改成Person p = new Person();代码依然无法通过编译,因为子类作用域中访问不到父类的私有变量,无法为其生成正确的字节码。另外,一个Java文件中不能有两个public类。

2.2子父类执行顺序

class B extends Object
{
    static
    {
        System.out.println("Load B");
    }
    public B()
    {
        System.out.println("Create B");
    }
}
class A extends B
{
    static
    {
        System.out.println("Load A");
    }
    public A()
    {
        System.out.println("Create A");
    }
}

public class Testclass
{
    public static void main(String[] args)
    {
        new A();
    }
}

答案 :

LoadB

LoadA

CreateB

CreateA

子父类的执行顺序:

  • 父类静态变量和静态初始化块,按在代码中出现的顺序依次执行。
  • 子类静态变量和静态初始化块,按在代码中出现的顺序依次执行。
  • 父类实例变量和实例初始化块,按在代码中出现的顺序依次执行。
  • 执行父类构造方法。
  • 子类实例变量和实例初始化块,按在代码中出现的顺序依次执行。
  • 执行子类构造方法。

2.3两同两小一大原则

  1. 两同子类的方法名和参数列表必须和父类完全一致
  2. 两小子类的返回值与父类的相同,或者是父类返回值的子类;子类的声明的异常与父类的相同,或者是父类中声明异常的子类。
  3. 一大子类的访问权限必须大于等于父类的访问权限。仅对于引用数据类型。(public > protect > 缺省 > private)

3.多重继承的概念体现方式

  1. 实现一个或多个接口
  2. 继承一个类并实现一个或多个接口
  3. 通过内部类去继承其他类

4.抽象类和接口的区别

  1. 对于定义方式:抽象类是一个类,可以包含有成员变量、成员方法和构造方法;而接口仅包含定义的常量和成员方法,没有方法体实现,也没有成员变量。
  2. 对于继承实现方式:一个类只能继承一个抽象类,而一个类可以同时实现多个接口。
  3. 对于抽象方法:抽象类可以定义抽象方法和非抽象方法,而接口仅能定义为抽象方法。
  4. 对于方法的实现:实现接口的类必须实现接口的所有方法,而继承抽象类的类必须实现所有的抽象方法(否则子类也必须声明为抽象方法),非抽象方法可以不实现。

5.权限修饰符

5.1外部类、内部类和局部内部类的权限修饰符

权限修饰符
外部类 public、缺省(default)
内部类 public、protected、缺省(default)、private
局部内部类 没有权限修饰符,只能在当前方法或语句块中访问

6.构造函数调用or继承?

构造函数不能被继承,构造方法只能被显式或隐式的调用

7.类型转化

7.1包装类类型转化

以下语句返回值为 true 的是()

Integeral = 17,a2 = 17;
Integer b1 = 2017,b2 = 2017;
Integerc1 = new Integer(17);
Integer c2 = new Integer(17);
Integer d1 = new Integer(2017);
int d2 = 2017;
A. al==a2
B. d1==d2 .
C. b1== =b2
D. c1==c2

答案:AB

对于A选项:a1、a2赋值给Integer类型,自动装箱。对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的Integer对象(并不是新建对象),变量所指向的是同一个对象,所以a1==a2返回true

选项B:d1为包装类Integer,而d2为int类型,Integer和int比较,Integer类型的数据会自动拆箱,所以B正确。b1、b2、c1、c2都创建的新对象,内存地址不同,C、D错误。

8.Java 中final、finally和finalize的区别

  1. final是一个关键字,可以用来修饰类、方法或变量。
    当一个类被声明为final时,表示该类不能被继承。当一个方法被声明为final时,表示该方法不能被子类重写。当一个变量被声明为final时,表示该变量是一个常量,不能再被修改。
  2. finally 是一个关键字,用于与 “try” 或 “catch” 一起使用,表示无论是否发生异常,都需要执行其中的代码块。
  3. finalize 是一个方法名,用于垃圾回收(GC)。在 Java 中,当对象不再被引用时,GC 会尝试清除该对象的内存,但在清除之前会调用该对象的 finalize() 方法进行资源回收。

9.==和equals()的区别

1. ==操作符
==操作符专门用来比较变量的值是否相同。

  • 基本数据类型:比较的是他们的值是否相同。
  • 引用数据类型:比较的是他们的内存地址是否同一地址。
    引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。

2. equals方法
equals方法常用来比较对象的内容是否相同。

Java当中所有的类都是继承于Object这个超类的,在Object类中定义的equals方法:

public boolean equals(Object obj) {
    return (this == obj);
}
  • 未重写equals方法的类:Object中的equals方法实际使用的也是==操作符,比较的是他们的内存地址是否同一地址。
  • 重写了equals方法的类:实现该类自己的equals方法比较逻辑(一般是比较对象的内容是否相同)。比如:
    • String:比较字符串内容,内容相同则相同;
    • Integer:比较对应的基本数据类型int的值是否相同(==操作符)。

10.Map接口和Entry接口的关系

Map接口和Entry接口是Java编程语言中的两个接口,两者之间存在一定的关系。

==具体来说,Map.Entry接口是Map接口的一个内部接口,它表示Map对象中的一个键值对(即一条记录)。 Map接口提供了访问Map对象中各个记录的方法,而Map.Entry接口则提供了访问记录中键和值的方法。==

因此,当我们需要对Map对象中的各个记录进行遍历或操作时,通常需要使用Map.Entry接口和Map接口配合使用。 例如:

Copy code
Map<String, Integer> map = new HashMap<>();

// 添加几条记录
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);

// 遍历Map对象,对各个记录进行操作
for(Map.Entry<String, Integer> entry : map.entrySet()){
    String key = entry.getKey();
    Integer value = entry.getValue();
    // do something with key and value
}

在这个示例中,我们先创建了一个Map对象,并添加了几条记录。 然后使用entrySet()方法获取Map对象中所有的记录,遍历每个记录(即每个Map.Entry对象),并使用getKey()和getValue()方法获取每个记录的键和值。 我们可以根据这些键值对执行任何想要的操作。

11.通配符和泛型的区别

通配符和泛型都是Java中用来处理不确定类型的关键字,但它们之间有以下区别:

  1. 使用位置:泛型通常用来指定类、方法或接口的参数类型,而通配符则通常用在作为函数参数的类型上。
  2. 粒度:泛型是“更粗”的类型,它可以指定具体的数据类型,如List,而通配符是“更细”的类型,它表示某些不确定类型的集合,如List<?>。
  3. 操作范围:使用泛型的代码可以对特定类型的对象进行操作,而使用通配符的代码则只能进行局部的一些操作,例如遍历集合。
  4. 单一性:通配符是一种单一的类型,可以表示任意类型的集合,而泛型可以指定一个或多个具体的类型参数。
  5. 语法:泛型使用<>表示类型参数,而通配符使用?表示未知的类型。通配符可以使用extends关键字限制类型范围,例如List<? extends Number>表示仅能包含Number及其子类类型的集合。

总体而言,泛型和通配符都是Java中非常重要的类型处理机制。泛型要更灵活一些,它能够具体地指定类型,通常用于类、方法或接口声明。而通配符则是更简单的处理机制,它用于对多个不同类型的对象进行操作。


Nowcoder Java专项练习
https://xhablog.online/2023/03/22/Nowcoder Java专项练习/
作者
Xu huaiang
发布于
2023年3月22日
许可协议