Java高频面试之基础篇

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家

Java高频面试之总纲篇

Java高频面试之集合篇

Java高频面试之异常篇

Java高频面试之并发篇

Java高频面试之SSM篇

Java高频面试之Mysql篇

Java高频面试之Redis篇

Java高频面试之消息队列与分布式篇

50道SQL面试题

奇奇怪怪的面试题

五花八门的内存溢出

解释下什么是面向对象?面向对象和面向过程的区别?

面向对象的三大特性?分别解释下?

  1. 封装

  2. 继承

  3. 多态

JDK、JRE、JVM 三者之间的关系?

JVM:将字节码文件转成具体系统平台的机器指令。
JRE:JVM+Java语言的核心类库。
JDK:JRE+Java的开发工具

重载和重写的区别?

重写注意事项有哪些(一大一同2小2不能 1122)

  1. 访问修饰符必须大于等于父类
  2. 方法签名必须相同(方法名+ 参数列表)
  3. 返回值必须小于等于父类
  4. 异常必须小于等于父类(可以不抛异常)
  5. private修饰的方法不能重写
  6. static修饰的方法不能重写

java.lang.Object#clone

protected native Object clone() throws CloneNotSupportedException;
public class Person {

    @Override
    public Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }
}

Java 中是否可以重写一个 private 或者 static 方法?

构造方法有哪些特性?

  1. 名字与类名相同。
  2. 没有返回值,但不能用void 声明构造函数。
  3. 生成类的对象时自动执行,无需调用。

在 Java 中定义一个不做事且没有参数的构造方法有什么作用?

Java 中创建对象的几种方式?

  1. 使用new关键字
  2. 使用反射
  3. clone方法
  4. 反序列化

抽象类和接口有什么区别?

  1. 接口自上而下是一种纯粹的规范,抽象类自下而上是一种抽象
  2. 接口表达是的能干什么,即实现某个接口具有某个接口定义的行为,抽象类表达的是是什么,即子类是 抽象类的特殊类型.
  3. 接口只有方法的声明,没有具体的实现(java8中接口中的方法可以有默认实现,即用default修饰,即便在java8中也不能有普通方法)抽象类中可以有普通方法.
  4. 接口中只能有常量不能包含成员变量(默认用 public static final修饰) ;抽象类中可以有成员变量
  5. 接口中不能含有静态代码块(可以有静态方法,但是只能通过接口调用,不能通过实现类调用,更不能通过实现类的对象调用),抽象类可以有
  6. 接口不能有构造方法,抽象类可以有
  7. 一个类只能继承一个抽象类,但是可以实现多个接口.

静态变量和实例变量的区别?

short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1;呢?有没有错误?

short s1 = 1;
// 会报错  s1 + 1运算时会自动提升表达式的类型,所以结果是int型, 所以需要将计算结果强转为short类型即s1 = (short) (s1 + 1);
// s1 = s1 + 1;
s1 = (short) (s1 + 1);
// 不会报错  += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
s1 +=1;

Integer 和 int 的区别?

装箱和拆箱的区别

switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?

switch可作用于char、byte、short、int及包装类上(4用)

switch不能可作用于long、double、float、boolean及包装类上(4不用)

jdk1.7后switch可作用于String上

// switch可作用于char byte short int
byte season4byte = 3;
char season4char = 3;
short season4short = 3;
int seasonint = 3;

// switch可作用于char byte short int对应的包装类
Byte season4Byte = 3;
Character season4Character = 3;
Short season4Short = 3;
Integer season4Integer = 3;

// switch不可作用于long double float boolean,包括他们的包装类
long season4long = 3;
double season4double = 3;
float season4float = 3f;
boolean season4boolean = false;

// 1.7之后可以作用在String上
String season4String = "";

String strSeason;
switch (season4Byte) {
    case 1:
        strSeason = "Spring";
        break;
    case 2:
        strSeason = "Summer";
        break;
    case 3:
        strSeason = "Fall";
        break;
    case 4:
        strSeason = "Winter";
        break;
    default:
        strSeason = "四季中没有这个季节";
        break;
}
System.out.println("strSeason:" + strSeason);

16、final、finally、finalize 的区别

final 是用来修饰类、方法、变量和参数的关键字;

finally 是 Java 中保证重点代码一定要被执行的一种机制;

finalize 是 Object 类中的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收的,但其执行“不稳定”,且有一定的性能问题,已经在 JDK 9 中被设置为弃用的方法了。

== 和 equals 的区别?

  1. ==一个运算符,equals 是方法
  2. ==比较变量的值是否相等(基本类型变量的值是值本身,引用类型变量的值是引用),equals 用来比较对象的类容是否相等

两个对象的 hashCode() 相同,则 equals() 也一定为 true 吗?

hashCode()相同,equals不一定相同

equals不同,hashCode一定不相同

为什么重写 equals() 就一定要重写 hashCode() 方法?

因为不同对象的hashCode 可能相同;但hashCode 不同的对象一定不相等,所以使用hashCode 可以起到快速初次判断对象是否相等的作用。

& 和 && 的区别?

&和&&都可以做逻辑运算符号,但&&又叫短路运算符。 因为当第一个表达式的值为false的时候,则不会再计算第二个表达式;而&则不管第一个表达式是否为真都会执行两个表达式。 另外&还可以用作位运算符,当&两边的表达式不是Boolean类型的时候,&表示按位操作。

Java 中的参数传递是传值呢?还是传引用?

java中只有值传递,没有引用传递

形参:方法列表中的参数

实参:调用方法时实际传入到方法列表的参数(实参在传递之前必须初始化)

值传递:传递的是实参的副本(更准确的说是实参引用的副本,因为形参接受的是对象的引用)

引用传递:传递的是内存地址

public static void main(String[] args) {
    // 实参
    int num = 1;
     // num 没有被改变 基本类型存储在栈里面,main方法栈里有一个num = 1,foo方法栈里存了一个副本num = 1;后来foo栈里面的改成了100,不会影响main方法中的
    foo(num);

    String str = "ABC";
    foo(str); // str 也没有被改变

    StringBuilder sb1 = new StringBuilder("iphone");
    foo1(sb1); // sb 被改变了,变成了"iphone4"。

    /*
     * main方法栈有有个sb2 指向堆中的StringBuilder("iphone")对象
     * 将main栈中的sb2的副本传递给foo2中的形参builder,builder指向堆中的StringBuilder("iphone")对象(与main是同一个对象)
     * foo2栈中的builder指向StringBuilder("ipad")对象
     * main栈中的sb2不会受影响
     * 如果是引用传递main中的sb2会收到影响
     */
    StringBuilder sb2 = new StringBuilder("iphone");
    foo2(sb2); // sb 没有被改变,还是 "iphone"


    System.out.println("num:" + num);//num:1
    System.out.println("str:" + str);//str:ABC
    System.out.println("sb1:" + sb1.toString());//sb1:iphone4
    System.out.println("sb2:" + sb2.toString());//sb2:iphone
}

//第一个例子:基本类型 value为形参
static void foo(int value) {
    value = 100;
}

//第二个例子:没有提供改变自身方法的引用类型
static void foo(String text) {
    text = "windows";
}

/*
 * 是否说明java支持引用传递呢? 不支持
 * StringBuilder builder传递的仅仅是builder本身的值(即实参引用的副本)
 */
static void foo1(StringBuilder builder) {
    builder.append("4");
}

//第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
static void foo2(StringBuilder builder) {
    builder = new StringBuilder("ipad");
}

Java 中的 Math.round(-1.5) 等于多少?

-1

如何实现对象的克隆?

有两种方式:

  1. 实现Cloneable接口并重写Object类中的clone()方法(必须实现Cloneable标记接口,否则会报CloneNotSupportedException异常)
  2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

深克隆和浅克隆的区别?

  1. 浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用(引用类型:新老对象指向同一个对象)。

  2. 深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,复制后的对象与原对象之间完全不会影响(手动调用引用类型的对象的clone方法)。

  3. 使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。

    @Data
    public class Yyy implements Cloneable {
        int id;
    
        @Override
        protected Yyy clone() throws CloneNotSupportedException {
            return (Yyy) super.clone();
        }
    
        public Yyy(int id) {
            this.id = id;
        }
    }
    

    浅克隆

    public class Xxx implements Cloneable {
        private int age;
        private Yyy yyy;
    
        @Override
        public Xxx clone() throws CloneNotSupportedException {
            // 只克隆了基本类型,引用类型和原对象指向同一个堆中的对象
            return (Xxx) super.clone();
        }
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Xxx xxx = new Xxx();
            xxx.setAge(1);
            xxx.setYyy(new Yyy(1));
            Xxx clone = xxx.clone();
            clone.getYyy().setId(2);
            System.out.println("xxx = " + xxx);
            System.out.println("clone = " + clone);
        }
    
    }
    

    深克隆

    public class Xxx implements Cloneable {
        private int age;
        private Yyy yyy;
    
        @Override
        public Xxx clone() throws CloneNotSupportedException {
            Xxx clone = (Xxx) super.clone();
            // 给引用类型重新赋值 调用引用类型的 clone方法
            clone.setYyy(this.getYyy().clone());
            return clone;
        }
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Xxx xxx = new Xxx();
            xxx.setAge(1);
            xxx.setYyy(new Yyy(1));
            Xxx clone = xxx.clone();
            clone.getYyy().setId(2);
            System.out.println("xxx = " + xxx);
            System.out.println("clone = " + clone);
        }
    
    }
    
    xxx = Xxx(age=1, yyy=Yyy(id=1))
    clone = Xxx(age=1, yyy=Yyy(id=2))
    

什么是 Java 的序列化,如何实现 Java 的序列化?

序列化:(方便在磁盘上存储或者在网络上传输)把对象转换为字节序列的过程称为对象的序列化。

反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

利用ObjectOutputStream和ObjectInputStream序列化和反序列化时一定要实现Serializable接口,否则会报NotSerializableException异常

利用fastjson序列化字符串时不实现Serializable不会报异常

@Data
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    //    private static final long serialVersionUID = 2L;
    public static int id;
    private String name;
    private int age;
    private Pet pet;
    private transient Car car;
    private int height;

    public static void main(String[] args) {
        serializePerson();
        deserializePerson();
    }

    private static void serializePerson() {
        Person person = new Person();
        person.setName("张三三");
        person.setAge(18);
        person.setPet(new Pet("大黄"));
        person.setCar(new Car("奥迪"));
        Person.id = 1;
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.txt"))) {
            out.writeObject(person);
            System.out.println("序列化完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void deserializePerson() {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.txt"))) {
            Person readObject = (Person) in.readObject();
            System.out.println("readObject = " + readObject);
            System.out.println(Person.id);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

@Data
class Car {
    private String name;

    public Car(String name) {
        this.name = name;
    }
}

@Data
class Pet implements Serializable {
    private String name;

    public Pet(String name) {
        this.name = name;
    }
}
序列化完成
readObject = Person(name=张三三, age=18, pet=Pet(name=大黄), car=null, height=0)
1

说明:

  1. 静态成员变量不能序列化(需要单独跑序列化,再跑反序列化)
  2. transient修饰的成员变量不参与序列化
  3. 参与序列化的引用类型也必须实现Serializable接口,否则会报NotSerializableException异常
  4. 最好提供serialVersionUID解决新老版本数据的兼容性问题(数据版本不一致时会报InvalidClassException异常)
  5. 可序列化类的所有子类型本身都是可序列化的。因为实现接口也是间接的等同于继承。
  6. 利用fastjson等工具时,不实现Serializable不会报错

什么情况下需要序列化?

把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
在网络上传送对象的字节序列。

Java 的泛型是如何工作的 ? 什么是类型擦除 ?

什么是泛型中的限定通配符和非限定通配符 ?

限定通配符包括两种:

  1. 表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类

  2. 表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类

  3. List<? extends T> src 只能读,获取到的元素时T类型或者子类型,List<? super T> dest只写,写进去的元素时T类型或者T类型的子类型(上限怎么理解??? List的类型为T或者T的父类,所以你存T或者T的子类是都可以的),读又写List list不限定类型

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<srcSize; i++)
           dest.set(i, src.get(i));
    }
    

非限定通配符:类型为,可以用任意类型来替代。

Java 中的反射是什么意思?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

有哪些应用场景?

  1. 框架和库开发:许多Java框架和库使用反射来实现插件化、扩展性和动态配置。例如,Spring框架使用反射来实现依赖注入和AOP(面向切面编程)等功能。
  2. 序列化和反序列化:反射可以在对象和字节流之间进行转换,实现对象的序列化和反序列化。常见的序列化框架,如Java的ObjectOutputStream和ObjectInputStream,以及JSON序列化库,通常使用反射来读取和写入对象的属性。
  3. 单元测试:反射可以在单元测试中模拟和操作私有字段和方法,以便进行更全面的测试。测试框架如JUnit和TestNG使用反射来调用测试方法并获取测试结果。
  4. 动态代理:反射可以用于创建动态代理对象,通过代理对象来拦截并执行方法调用。这种机制在AOP中经常用到,可以在不修改原始类的情况下添加额外的逻辑。
  5. 配置文件解析:通过反射可以读取和解析配置文件,将配置文件中的数据映射到Java对象中。例如,常见的XML解析库,如DOM和SAX解析器,可以使用反射来创建并操作对象。
  6. 注解处理:反射可以用于处理和解析注解。通过反射,可以获取类、字段、方法上的注解,并根据注解的信息执行相应的操作,如生成文档、生成代码等。

反射的优缺点?

  1. 性能开销:与直接调用静态代码相比,使用反射调用方法和访问字段会导致一定的性能开销。反射涉及到运行时的类型检查和方法调用解析,通常比静态代码执行更慢。
  2. 安全性限制:反射可以绕过访问控制修饰符(如private),从而访问和修改本来不应该被公开的成员。这可能会破坏封装性和安全性,因此在使用反射时需要特别小心,确保不会引入潜在的安全漏洞。
  3. 编译时类型检查缺失:由于反射允许在运行时动态地获取类的信息,因此编译器无法进行静态类型检查。这可能导致在编译时无法捕获到某些错误,而是在运行时才会出现异常。

Java 中的动态代理是什么?有哪些应用?

Java中的动态代理是一种在运行时生成代理对象的机制。它允许在不预先编写具体代理类的情况下创建代理对象,并且代理对象可以在运行时拦截并处理对目标对象的方法调用。

动态代理的应用场景

  1. 日志记录:动态代理可以在方法调用前后记录日志。
  2. 事务管理:动态代理可以在方法调用前后处理事务的开启、提交或回滚,从而实现对事务的控制。
  3. 安全检查:动态代理可以在方法调用前进行安全检查,例如检查用户权限或身份验证。
  4. 缓存管理:动态代理可以在方法调用前检查缓存,如果缓存中存在结果,则直接返回缓存的结果,否则调用目标方法并将结果缓存起来。
  5. 延迟加载:动态代理可以延迟加载目标对象,当真正需要调用方法时才进行初始化,从而提高性能例如spring的**@Lazy**注解。
  6. AOP
  7. @Lookup
  8. 远程调用:动态代理可以用于远程通信,代理对象将方法调用转发给远程服务,并将结果返回给调用方。

怎么实现动态代理?

  1. JDK动态代理
PersonServiceImpl target = new PersonServiceImpl();

// UserInterface接口的代理对象
// Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Object proxy = Proxy.newProxyInstance(PersonService.class.getClassLoader(), new Class[]{PersonService.class}, (proxy1, method, args1) -> {
   System.out.println("before...");
   Object result = method.invoke(target, args1);
   System.out.println("after...");
   return result;
});

PersonService userService = (PersonService) proxy;
userService.addPerson(new Person("张三三"));
  1. cglib动态代理
PersonServiceImpl target = new PersonServiceImpl();

// 通过cglib技术 /ɪnˈhɑːnsə(r)/ 增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonServiceImpl.class);

// 定义额外逻辑,也就是代理逻辑
enhancer.setCallbacks(new Callback[]{(MethodInterceptor) (o, method, objects, methodProxy) -> {
   System.out.println("before...");
   Object result = methodProxy.invoke(target, objects);
   System.out.println("after...");
   return result;
}});

// 动态代理所创建出来的UserService对象
PersonServiceImpl personService = (PersonServiceImpl) enhancer.create();

// 执行这个userService的test方法时,就会额外会执行一些其他逻辑
personService.addPerson(new Person("张三三"));

static 关键字的作用?

super 关键字的作用?

一个指向父类对象的引用变量

字节和字符的区别?

String 为什么要设计为不可变类?

因为Sting类型在java中用的最多,所以做了特殊处理,为了提升性能,性能提升了,但是创建了很多对象,怎么破?常量池

Java中的字符串(String)被设计为不可变的主要有以下几个原因:

  1. 性能优化:字符串池(String Pool)的存在使得多个字符串变量可以共享同一个字符串对象。如果字符串是可变的,那么在修改字符串时就需要创建一个新的字符串对象,导致内存的频繁分配和回收,影响性能。而不可变的字符串可以在字符串池中复用现有对象,避免了额外的内存开销。
  2. 安全性:字符串在Java中广泛用于作为参数传递给方法、用于作为HashMap的键等。如果字符串是可变的,那么在这些情况下,字符串对象的值可以被修改,从而可能导致意外的行为或安全漏洞。通过将字符串设计为不可变的,可以确保字符串的值不会被修改,提高了代码的安全性。
  3. 线程安全:不可变性是线程安全的一种保证。在多线程环境下,如果多个线程同时修改同一个可变字符串对象,就会引发竞态条件(race condition)和不一致的结果。通过使用不可变的字符串,可以避免这种问题,无需额外的同步措施。
  4. 缓存哈希值:由于字符串在Java中经常被用作HashMap的键,字符串的不可变性确保了字符串的哈希值不会变化。这样在使用字符串作为键进行查找时,可以更快地定位到正确的位置,提高了性能。

综上所述,将字符串设计为不可变的是为了性能优化、安全性、线程安全和哈希值缓存等方面的考虑。这是Java语言设计的一项重要决策。

String、StringBuilder、StringBuffer 的区别?

  1. 可变性:String是不可变的类,一旦创建了String对象,它的值就不能被改变。而StringBuilder和StringBuffer是可变的类,可以修改已有的字符串内容。
  2. 线程安全性:String是线程安全的,因为它是不可变的,多个线程可以安全地共享String对象。StringBuilder是非线程安全的,而StringBuffer是线程安全的。在多线程环境下,如果需要对字符串进行频繁的修改操作,应使用StringBuffer,它会进行同步控制以保证线程安全性。
  3. 性能:由于String是不可变的,每次对String进行修改时,实际上都会创建一个新的String对象,这可能导致内存开销增加。而StringBuilder和StringBuffer是可变的,它们在进行字符串修改时不会创建新的对象,而是在原有对象上直接进行操作,因此在性能上比String更高效。StringBuilder相对于StringBuffer稍微快一些,因为StringBuilder不进行同步控制。

综上所述,当字符串需要频繁修改时,应优先使用StringBuilder。如果在多线程环境下需要对字符串进行修改,应使用StringBuffer以保证线程安全性。而当字符串不需要修改时,或者需要在多个线程之间共享时,应使用String,以保证安全性。

String str = “i” 与 String str = new String(“i”) 一样吗?

不一样,String str = “i”;是把值放到了常量中,而String str = new String(“i”);是将值放到了堆内存中。

String 类的常用方法都有那些?

String s = "hello string ";
s.length();
s.trim();
s.contains("");

s.startsWith("");
s.endsWith("");

s.toLowerCase();
s.toUpperCase();

s.replace("","");
s.replaceFirst("","");

s.indexOf("");
s.lastIndexOf("");

s.substring(3);
s.split(" ");
s.matches("");
s.charAt(1);
s.intern();
s.toCharArray();

String.join(",","1","2");
String.format("hello %s","zss");
String.valueOf(1);

字符串比较

  1. 通过字面量赋值创建字符串(如:String s=”hi”),会先在常量池中查找是否存在相同的字符串,若存在,则直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将引用指向该字符串。

    image.png

  2. 通过new String创建字符串,在堆上创建一个,同时在常量池创建一个值相同的对象,但是这两个对象互不相干,如果常量池里已经有了同样的值的对象,只会在堆里新建对象

    image.png

  3. 常量字符串和变量拼接时或者变量与变量拼接时会调用stringBuilder.append()在堆上创建新的对象,而不会同时在常量池里新建对象

    String s = new String("a") + new String("b");
    

    约等于

    StringBuilder sb = new StringBuilder();
    sb.append("a").append("b");
    // toString()只会在堆上创建对象("ab"),new String("ab") 会在堆上和常量池都创建
    String s = sb.toString();
    

    image.png

  4. 调用字符串对象的 intern() 方法时,intern方法会先去常量池找,如果存在,指向常量池中的,如果不存在,在常量池中生成一个对原字符串的引用

    image.png

  5. 字面量+字面量在编译期间就优化成了常量

    String s = new String("a") + new String("b");
    String intern = s.intern();
    System.out.println(s == intern);// true
    
    String str1 = "哈哈";
    String str2 = str1 + "呵呵";
    String str3 = "哈哈呵呵";
    System.out.println(str2 == str3);// false
    
    String s1 = "abc";
    String s2 = "a";
    String s3 = "bc";
    String s4 = s2 + s3;
    System.out.println(s1 == s4);// false
    

    注意这个与上一个对比

    String s1 = "abc";
    final String s2 = "a";
    final String s3 = "bc";
    String s4 = s2 + s3;
    // 因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”
    System.out.println(s1 == s4);//true
    
    String s = new String("abc");
    String s1 = "abc";
    String s2 = new String("abc");
    System.out.println(s == s1.intern());// false
    System.out.println(s == s2.intern());// false
    System.out.println(s1 == s2.intern());// true
    
    String a = "a";
    String b = "b";
    String ab = a+b;
    String ab2 = a+b;
    String Iab = ab.intern();
    String Iab2 = ab2.intern();
    // ab与ab2都在堆里
    System.out.println(ab==ab2);// false
    // 常量池中没有"ab"即String ab = a+b;也等价于sb.append("a").append("b");
    System.out.println(Iab==ab);// true
    System.out.println(Iab2==ab);// true
    

你到底懂了吗

public class StringDemo {
    public static void main(String[] args) {
        m7();
    }

    private static void m1() {
        String a = "a";
        String b = "b";
        String s = a + b;
        String intern = s.intern();
        System.out.println(s == intern);
    }

    private static void m2() {
        String s = new String("a") + new String("b");
        String intern = s.intern();
        System.out.println(s == intern);
    }

    private static void m3() {
        String a = "a";
        String ab1 = a + "b";
        String ab2 = "ab";
        System.out.println(ab1 == ab2);
    }

    private static void m4() {
        String a = "a";
        String b = "b";
        String ab1 = a + b;
        String ab2 = "ab";
        System.out.println(ab1 == ab2);
    }

    private static void m5() {
        final String a = "a";
        String ab1 = a + "b";
        String ab2 = "ab";
        System.out.println(ab1 == ab2);
    }

    private static void m6() {
        String ab1 = "ab";
        String ab2 = new String("ab");
        String ab3 = new String("ab");
        System.out.println(ab2 == ab1.intern());
        System.out.println(ab2 == ab3.intern());
        System.out.println(ab2.intern() == ab3.intern());
        System.out.println(ab1 == ab3.intern());
    }

    private static void m7() {
        String a = "a";
        String b = "b";
        String ab1 = a + b;
        String ab2 = a + b;
        System.out.println(ab1 == ab2);
        System.out.println(ab1 == ab1.intern());
        System.out.println(ab2 == ab2.intern());
        System.out.println(ab1 == ab2.intern());
        System.out.println(ab1.intern() == ab2.intern());
    }

}

image.png

final 修饰 StringBuffer 后还可以 append 吗?

可以 ,final 修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。

Java 中的 IO 流的分类?说出几个你熟悉的实现类?

image.png

常见的字节流

FileInputStream

try (FileInputStream inputStream = new FileInputStream("input.txt")) {
    int data;
    while ((data = inputStream.read()) != -1) {
        System.out.println((char) data);
    }
} catch (IOException e) {
    e.printStackTrace();
}

BufferedInputStream

try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("input.txt"))) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        System.out.write(buffer, 0, bytesRead);
    }
} catch (IOException e) {
    e.printStackTrace();
}

FileOutputStream

try (FileOutputStream outputStream = new FileOutputStream("output.txt")) {
    String data = "Hello, World!";
    byte[] bytes = data.getBytes();
    outputStream.write(bytes);
} catch (IOException e) {
    e.printStackTrace();
}

BufferedOutputStream

try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
    String data = "Hello, World!";
    byte[] bytes = data.getBytes();
    outputStream.write(bytes);
} catch (IOException e) {
    e.printStackTrace();
}

常见的字符流

FileReader

try (FileReader reader = new FileReader("example.txt")) {
    int character;
    while ((character = reader.read()) != -1) {
        System.out.println((char) character);
    }
} catch (IOException e) {
    e.printStackTrace();
}

BufferedReader

public static void main(String[] args) {
    try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

FileWriter

try (FileWriter writer = new FileWriter("output.txt")) {
    String data = "Hello, World!";
    writer.write(data);
} catch (IOException e) {
    e.printStackTrace();
}

BufferedWriter

try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
    String line1 = "Line 1";
    String line2 = "Line 2";
    writer.write(line1);
    writer.newLine(); // 写入换行符
    writer.write(line2);
} catch (IOException e) {
    e.printStackTrace();
}

字节流和字符流有什么区别?

  1. 数据处理单位:字节流以字节为单位进行数据传输,而字符流以字符为单位进行数据传输。
  2. 编码处理:字节流以原始的字节形式传输数据,不对数据进行任何编码或解码。字符流则可以进行字符编码和解码的操作,将字符按照指定的字符集转换为字节流进行传输,或将字节流按照指定的字符集转换为字符进行处理。
  3. 应用场景:字节流常用于处理图像、音频、视频等二进制文件,或者在处理文本文件时不涉及字符编码的情况。字符流常用于处理文本文件,尤其是需要考虑字符编码和国际化的情况。
  4. 继承体系:字符流的父接口为Reader/Writer。字节流父接口为InputStream/OutputStream。

需要注意的是,字符流底层仍然是通过字节流来进行数据传输的,字符流会在字节流的基础上进行字符编码和解码的处理。在处理文本数据时,字符流更为方便(推荐),因为它们可以自动处理字符集的转换。

BIO、NIO、AIO 有什么区别?

  1. 阻塞与非阻塞:BIO是阻塞式的I/O模型,即当线程执行I/O操作时,会被阻塞直到操作完成。NIO和AIO是非阻塞式的I/O模型,允许线程在等待I/O操作完成时继续做其他工作。
  2. 操作方式:BIO使用同步阻塞方式进行I/O操作,即一个线程对应一个客户端连接或一个I/O操作。NIO使用同步非阻塞方式,通过使用选择器(Selector)和缓冲区(Buffer)实现对多个客户端连接的管理和数据读写。AIO使用异步非阻塞方式,通过回调机制实现对I/O操作的处理。
  3. 线程模型:BIO采用了一对一的线程模型,即每个客户端连接都需要一个独立的线程进行处理。NIO采用了多路复用器(Selector)和事件驱动模型,通过少量线程管理多个连接。AIO也采用了事件驱动模型,但不需要主动通过多路复用器轮询事件,而是通过回调函数的方式实现异步处理。
  4. 编程复杂性:BIO的编程模型相对简单,但在面对大量并发连接时,需要创建大量线程,造成资源消耗和线程切换开销。NIO相对于BIO更复杂一些,需要处理事件的选择和缓冲区的管理,但能够处理大量并发连接而不需要过多的线程。AIO在编程模型上更加复杂,需要使用回调函数来处理异步操作,但可以实现高并发和高吞吐量的I/O操作。

根据实际需求和场景的不同,可以选择适合的I/O模型。BIO适用于连接数较小且简单的情况,NIO适用于需要管理大量连接的情况,AIO适用于需要实现高并发和高吞吐量的异步操作的情况。

Java高频面试之总纲篇

Java高频面试之集合篇

Java高频面试之异常篇

Java高频面试之并发篇

Java高频面试之SSM篇

Java高频面试之Mysql篇

Java高频面试之Redis篇

Java高频面试之消息队列与分布式篇

50道SQL面试题

奇奇怪怪的面试题

五花八门的内存溢出

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java基础高频面试题包括以下内容: 1. 你是怎么理解面向对象的? 2. String、StringBuilder、StringBuffer的区别是什么? 3. ==与equals的区别是什么? 4. 什么是反射? 5. Java支持多继承吗? 6. 进程和线程的区别是什么? 7. 不考虑数据库的隔离性会发生什么? 8. 数据库的隔离级别有哪些? 9. 面向对象的三个基本特征是什么? 10. 访问修饰符public、private、protected以及不写时的区别是什么? 11. 给出一些代码块,判断是否能正常编译和执行。 12. 给出一些代码块,预测输出结果。 13. 如何用最有效率的方法计算2乘以8? 14. &和&&的区别是什么? 15. String是Java基本数据类型吗? 16. String类可以继承吗? 以上是一些Java基础高频面试题的例子,希望对你有帮助。 #### 引用[.reference_title] - *1* [Java基础高频面试题](https://blog.csdn.net/qsw1364941774/article/details/124499655)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Java高频面试题及答案整理(会持续更新)](https://blog.csdn.net/qq_44867340/article/details/112879620)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【吐血整理】2022年Java 基础高频面试题及答案(收藏)](https://blog.csdn.net/m0_67698950/article/details/126549157)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值