【面试】浅学Java基础

目录

Java中基本数据类型有哪些

Integer 和 int的区别

自动装箱和自动拆箱的原理

Integer和int的深入对比:

堆和栈

static关键字的作用?

final关键字的作用?

synchronized关键字的作用?

String和StringBuilder和StringBuffer区别?

String a = "aa" 和 String a = new String("aa") 创建字符串的区别

== 和 equals 的区别是什么

equals和hashCode的区别?

为什么重写了hashcode方法还需要重写equals方法?

final 和 finally 和 finalize 的区别

Java是如何实现跨平台的?

JDK 、JRE、JVM 有什么区别和联系?

面向对象和面向过程的区别

面向对象四大特性

封装(Encapsulation)

继承(Inheritance)

多态(Polymorphism)

编译看左边执行看右边

编译看右边执行看右边

抽象(Abstraction)

Java抽象类示例代码举例:

接口示例代码和测试类:

方法重写和重载

为什么构造方法不能被重写?

普通类和抽象类的异同?

为什么要使用抽象类?

public、protected,default和private的区别是什么?

接口和抽象类的异同

抽象类示例:

接口示例:

IO流

你知道BIO,NIO,AIO么?

Java 中四大基础流

读文本用什么流,读图片用什么流

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

BufferedInputStream(缓冲字节输入流) 用到什么设计模式

怎么实现一张图片拷贝

怎么实现文本拷贝

深拷贝和浅拷贝?

创建对象的方式有哪些?

1.使用new关键字创建对象

2.使用Class类的newInstance()方法创建对象

3.使用构造方法创建对象

4.使用反射创建对象

5.clone的方式

Java8新特性常用API

Java length() 方法、length 属性和 size() 方法有什么区别?

数组和集合的区别?

String常用API

(一)常见String类的获取功能

(二)常见String类的判断功能

(三)常见String类的转换功能

(四)常见String类的其他常用功能

Object常用的API


Java中基本数据类型有哪些

byte,short,int,long,float,double,boolean,char.

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

short:16位,

int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,

double:64位,

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。eg:'a';

Integer 和 int的区别

int基本数据类型,变量中直接存放数值,变量初始化时值是0,将数据存放在常量池中。

Integer引用数据类型(是一个类,可以进行new),变量中存放的是该对象的引用地址,变量初始化时值时*null,将数据存放在堆内存中 【开发中都是用的包装类-基本类型不是类,不能new出来,因此不具备面对对象的功能,无法调用方法。】

Integer是int类型的包装类,将int封装成Integer,符合java面向对象的特性,可以使用各种方法比如和其他数据类型间的转换。

自动装箱和自动拆箱的原理

自动装箱时就是将编译器将基本数据类型调用valueOf()方法转换成包装类对象。

自动拆箱就是编译器将包装类调用IntValue(),doubleValue()方法转换成基本数据类型。

Integer和int的深入对比:

  1. 两个通过new生成的Integer对象,由于在堆中地址不同所以永远不相等【因为是final修饰的,每次都会创建一个新的对象。】

  2. int和Integer比较时只要数值相等,结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int

  3. 通过new生成的Integer对象非通过new生成的Integer对象相比较时,由于前者存放在堆内存中堆内存存储的是对象】,后者存放在Java常量池【基本数据的对象和数组等】中,所以永远不相等

  4. 两个非通过new生成的Integer对象比较时,如果两个变量的数值相等且在-128到127之间,结果就相等。这是因为给Integer对象赋一个int值,java在编译时,会自动调用静态方法valueOf(),根据java api中对Integer类型的valueOf的定义,对于-128到127之间的整数,会进行缓存,如果下次再赋相同的值会直接从缓存中取,即享元模式

名词解释:享元模式是设计模式中少数几个以提高系统性能为目的的模式之一。它的核心思想是:如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用都创建新的对象。

堆和栈

堆(heap memory)和栈(stack memory)都是Java虚拟机(JVM)在运行时使用的内存区域。

堆是Java虚拟机管理的内存区域之一,用于存储Java对象的数据(例如,实例变量、数组等)。在Java程序运行时,如果需要创建对象,在堆内存中分配一块区域来存储这个对象的数据。

栈是程序员可以直接访问的内存空间,用于存储基本数据类型(例如,int、char等)的值和对象的引用。

static关键字的作用?

就是方便在没有创建对象的情况下进行调用(方法和变量)。

static修饰的成员方法为静态方法,直接通过类名进行调用,在当前类可以直接省略类名。

static修饰的成员方法不能访问非static修饰的成员变量和成员方法。

 static修饰的方法不能被重写可以被继承【static修饰,通过类名进行调用

非static修饰的成员方法需要通过对象进行调用。

static修饰的成员变量为静态变量,静态变量和非静态变量的区别:

静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化

非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

final关键字的作用?

  1. final修饰的类,为最终类,该类不能被继承。如String 类

  2. final修饰的方法可以被继承和重载但不能被重写

  3. final修饰的变量不能被修改,是个常量。

synchronized关键字的作用?

        synchronized关键字用于解决线程安全问题的,它可以修饰一个代码块、一个方法或一个类。当一个线程获取到了一个被synchronized修饰的对象锁,它将独占这个对象并执行其中的代码,直到完成后释放锁,其他线程才能继续执行。

它可以修饰的对象有以下几种:

1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

public void doSomething(){
    synchronized(this){
        // 这里的代码是同步的,同一时间只能被一个线程执行
    }
}

2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

public synchronized void doSomething(){
    // 这里的代码是同步的,同一时间只能被一个线程执行
}

3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

public synchronized static void doSomething(){
    // 这里的代码是同步的,同一时间只能被一个线程执行
}

4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
​​​​​

public void doSomething(){
    synchronized(MyClass.class){
        // 这里的代码是同步的,同一时间只能被一个线程执行
    }
}

String和StringBuilder和StringBuffer区别

        三者底层都是基于char[]数组存储数据。JDK1.9之后使用的是byte[]数组 ,因为往往我们存储都是短字符串,使用byte[]这样更节约空间。

        由于String是基于不可变char数组(底层final修饰的)的字符串所以是不可变的对象每次对String的操作都会在堆内存中生成新的对象

        StringBuilder和StringBuffer是基于可变数组的字符串【自动进行扩容】,StringBuffer是线程安全的【多线程】,方法有synchronized修饰,但是性能较低,StringBuilder是线程不安全的【单线程】,方法没有synchronized修饰,性能较高。

String a = "aa" 和 String a = new String("aa") 创建字符串的区别

String a = “aa”;这句话执行过程是:

先到常量池中寻找“aa”,

1.如果存在,则直接将【原有】“aa”对象地址传递给a;

2.如果不存在,则在常量池中创建“aa”,然后将地址传递给a.

String a = new String(“aa”);执行过程是:

首先在堆内存(存储对象)创建对象new String(“aa”),【堆内存存储对象】,将对象的引入传递给a

然后在常量池中寻找“aa”,

1.如果存在,啥事不做;

2.如果不存在,在常量池创建一个“aa”对象

总结可见;直接赋值形式的新建string对象是从常量池中拿数据;最多创建一个string对象,最少创建0个string对象;

new形式新建string对象无论怎样会首先在堆内存中创建一个string对象,然后确保常量池中也有一个相同内容的string对象;最多创建2个最少创建1个

由于直接赋值方式可能节约内存,推荐使用该方式。

String a = “abc“ 和 String a = new String(“abc“) 创建字符串的区别_GuGuBirdXXXX的博客-CSDN博客

== 和 equals 的区别是什么

“==”

1,比较8大基本数据类型byte,short,int,long,float,double,char,boolean):

==比较是是否相等。

2,比较6大引用数据类型类,接口,抽象类,枚举,注释,数组

==比较是地址值是否相等。

equals

1,只能比较引用数据类型(因为基本类型不能调用方法

equals比较的是内容是否相同。

equals和hashCode的区别?

重点结论

前提是两个对象都重写了hashCode和equals方法,

equals相等的两个对象,hashCode一定相等。

hashCode相等的两个对象,equals不一定相等。

上述结论解释:

equals比较的引用数据类型比较的是两个对象的内容是否相等,而hashCode比较的是两个对象的哈希值是否相等(两个内容不同的值生成的哈希值可能也是相同的,所以再进行equals比较的时候,就可能不相等。这就是所谓的哈希碰撞**(HashMap底层原理会有哈希碰撞))

顺带提一下

equals和hashCode,一个是性能好(hashCode),一个是可靠性(equals)。

equals的比较的逻辑比较全面复杂,效率较低,而hashCode比较只用生成一个哈希值就可以了,效率高。

为什么重写了hashcode方法还需要重写equals方法?

首先equals比较的是引用数据类型的内容是否相等,而hashcode比较的是hash值是否相等。

为了提高效率 采取重写hashcode方法,先进行hashcode比较,如果不同【说明两个对象一定不同】,那么就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的。

final 和 finally 和 finalize 的区别

当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰。

finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源(关流),释放锁的情况下

finalize()【结束】是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。执行时机:当一个java对象即将被垃圾回收器回收的时候垃圾回收器GC负责调用finalize()方法

其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。

GC【Garbage Controller】垃圾回收机制

有了GC,程序员就不需要再手动的去控制内存的释放。当Java虚拟机(JVM)发觉内存资源紧张的时候,就会自动地去清理无用对象(没有被引用到的对象)所占用的内存空间(这里的说法略显粗略,事实上何时清理内存是个复杂的策略)。如果需要,可以在程序中显式地使用System.gc() / System.GC.Collect()来强制进行一次立即的内存清理。Java提供的GC功能可以自动监测对象是否超过了作用域,从而达到自动回收内存的目的,Java的GC会自动进行管理,调用方法:System.gc() 或者Runtime.getRuntime().gc();

常用的垃圾回收算法有三种:标记-清除算法、复制算法、标记-整理算法,分代回收 【新生代和老年代】

Java是如何实现跨平台的?

跨平台:是指java语言编写的程序,一次编译,可以在多个系统运行。

如何实现跨平台JDK中javac编译器将java文件编译成字节码文件(.class文件),通过JVM【java虚拟机】将字节码文件通过类加载器编译成不同系统【windows、linux、Mac】能够识别的二进制机器码,这样就实现了一次编译,到处(多个系统平台上)运行。【关键因素就是系统是否安装相应的虚拟机。java程序实际是在虚拟机JVM上运行的】

JDK 、JRE、JVM 有什么区别和联系?

JDK(Java Development Kit) :是Java开发工具包,它提供了Java的开发环境(提供了编译器javac等工具,用于将java文件编译为.class[字节码]文件)和运行环境(提供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。JDK是整个Java的核心,包括了Java运行环境(JRE),一堆Java工具tools.jar和Java标准类库 (rt.jar)。

JRE(Java Runtime Enviroment) :是Java的运行环境,JRE是运行Java程序所必须的环境,包含JVM及 Java核心类库**

三者之间的关系:

JDK = JRE + 其他(一堆java工具(javac编译器)和java核心类库)

JRE = JVM + 其他(runtime class libraries等组件)

JDK【Java Development Kit】是 Java 语言的开发工具包。在JDK的安装目录下有一个jrejava runtime environment】目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm【java virtual machine】,lib中则是jvm工作所需要的核心类库,而jvmlib合起来就称为jre。

面向对象和面向过程的区别

        面向对象是一种基于对象的程序设计方法,将问题看作若干个相互关联的对象,通过对象之间的相互交互协作来处理问题。

        面向过程是一种基于步骤的程序设计方法,将问题分解成一系列步骤,从头到尾逐步执行

面向对象四大特性

封装(Encapsulation)

        封装是面向对象编程的一种基本概念,它指的是将数据和行为(即方法)包装在一个类中并对外部隐藏其内部具体实现的细节只对外部提供受保护的接口进行访问。这样可以确保程序的可靠性和安全性,使得程序的设计更加合理和紧凑。

Java代码举例:

public class Student {
    private String name; // 成员变量name是私有的
    private int age; // 成员变量age是私有的

    // getter和setter方法用于获取和修改私有成员变量
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

继承(Inheritance)

        继承是指一个类可以继承另一个类的属性和方法。被继承的类称为父类(超类、基类),继承的类称为子类(派生类、衍生类),子类可以通过继承从父类中获取属性和方法,并可以在此基础上进行扩展和修改(重写父类的方法)。继承使得程序的设计更加高效和灵活。

Java代码举例:

// 父类Person
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        System.out.println("Hello, I'm " + name + ", " + age + " years old.");
    }
}

// 子类Student继承自Person类
public class Student extends Person {
    private String school;

    public Student(String name, int age, String school) {
        super(name, age); // 调用父类Person的构造方法
        this.school = school;
    }

    public void study() {
        System.out.println(name + " is studying in " + school);
    }
}

多态(Polymorphism)

        多态是一个对象可以表现出多种状态的能力是指同一个方法在不同情况下会有不同的表现形式。多态有两种形式,一种是方法重载(overloading),即在同一个类中定义多个名称相同但参数列表个数顺序不同的方法;另一种是方法重写(overriding),即在子类中定义与父类同名、参数列表和返回值类型都相同的方法但实现不同。

Java代码举例:

// 父类Animal
public class Animal {
    public void sound() {
        System.out.println("I'm an animal.");
    }
}

// 子类Dog继承自Animal类,并重写父类的sound方法实现多态
public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("I'm a dog, woof woof.");
    }
}

// 子类Cat继承自Animal类,并重写父类的sound方法实现多态
public class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("I'm a cat, meow meow.");
    }
}

编译看左边执行看右边

class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    public void bark() {
        System.out.println("Dog is barking");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        animal1.eat(); // 输出 Animal is eating
        animal2.eat(); // 输出 Dog is eating
    }
}

        在上述代码中,通过一个 Animal 类和一个 Dog 类继承自 Animal 类,实例化了两个对象,一个 Animal 类型和一个 Dog 类型。此时,由于父类和子类的 eat() 方法都存在,所以编译器会根据左侧变量的类型来确定调用的方法。当执行 animal1.eat() 时,由于 animal1 的类型是 Animal,因此调用了父类的 eat() 方法,输出 Animal is eating;当执行 animal2.eat() 时,由于 animal2 的类型是 Dog,因此调用了子类的 eat() 方法,输出 Dog is eating。

编译看右边执行看右边

class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    public void bark() {
        System.out.println("Dog is barking");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Dog dog = new Dog();
        Animal animal2 = dog;
        dog.eat(); // 输出 Dog is eating
        animal2.eat(); // 输出 Dog is eating
    }
}

        在上述代码中,同样实例化了一个 Animal 对象和一个 Dog 对象,并将 Dog 对象赋值给一个 Animal 类型的引用。此时编译器同样会根据左侧变量的类型来确定调用的方法,由于 animal2 的实际类型是 Dog,所以在执行 animal2.eat() 时同样会调用子类的 eat() 方法,输出 Dog is eating。由此可见,多态的实现既依赖于对象的实际类型,也依赖于编译时变量的类型。

抽象(Abstraction)

        抽象是指将现实中的实体的公共特征抽象出来,定义成一个概念和模板而非具体实例。使得设计更加具有通用性和可扩展性。抽象可以通过抽象类和接口来实现

Java抽象类示例代码举例:

// 定义一个抽象类
public abstract class Shape {
    // 抽象方法,获取图形的面积
    public abstract double getArea();
    
    // 具体方法,获取图形的类型
    public String getType() {
        return "Unknown Shape";
    }
}

// 定义一个实现了Shape抽象类的子类,表示一个矩形
public class Rectangle extends Shape {
    private double width;
    private double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    // 实现抽象类Shape中的抽象方法getArea(),计算矩形面积
    public double getArea() {
        return width * height;
    }
    
    // 重写Shape中的getType()具体方法,返回矩形类型
    public String getType() {
        return "Rectangle";
    }
}

// 定义一个实现了Shape抽象类的子类,表示一个圆形
public class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    // 实现Shape中的抽象方法getArea(),计算圆形面积
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    // 重写Shape中的getType()具体方法,返回圆形类型
    public String getType() {
        return "Circle";
    }
}

// 定义一个测试类
public class Test {
    public static void main(String[] args) {
        Shape s1 = new Rectangle(3, 4);
        Shape s2 = new Circle(5);
        System.out.println("Type of s1: " + s1.getType() + ", Area: " + s1.getArea());
        System.out.println("Type of s2: " + s2.getType() + ", Area: " + s2.getArea());
    }
}

在上面的代码中, Shape类是一个抽象类,其中定义了一个抽象方法getArea()和一个具体方法getType(),所有继承自Shape的子类都需要实现getArea()方法RectangleCircle继承自Shape,并实现了getArea()方法和getType()方法,分别表示矩形和圆形。在测试类Test中,我们创建了一个Rectangle对象s1和一个Circle对象s2,并分别调用它们的getType()getArea()方法,打印出对象的类型以及面积。这种方法可以实现对多种不同类型的图形面积进行计算,极大地提高了代码的灵活性和可扩展性。

接口示例代码和测试类:

// 定义一个Printable接口
public interface Printable {
    // 定义一个抽象方法
    public void print();
}

// 定义一个可以打印的文本类
public class Text implements Printable {
    private String text;
    
    public Text(String text) {
        this.text = text;
    }
    
    // 实现Printable接口的print()方法
    public void print() {
        System.out.println(text);
    }
}

// 定义一个可以打印的图片类
public class Image implements Printable {
    private String filename;
    
    public Image(String filename) {
        this.filename = filename;
    }
    
    // 实现Printable接口的print()方法
    public void print() {
        System.out.println("打印图片: " + filename);
    }
}

// 定义一个测试类
public class Test {
    public static void main(String[] args) {
        Printable p1 = new Text("打印一段文本");
        Printable p2 = new Image("test.png");
        print(p1);
        print(p2);
    }
    
    // 打印Printable对象
    public static void print(Printable p) {
        p.print();
    }
}

在上面的代码中,Printable接口定义了一个print()抽象方法,Text类和Image类都实现了这个方法并重写了它的具体实现。在测试类Test中,我们创建了一个Printable类型的对象,并将它传递给print()方法,这个方法内部会调用对象的print()方法,实现了打印操作。这种方式极大地提高了代码的灵活性和可扩展性,我们可以通过实现Printable接口创建很多不同类型的打印对象,并将它们传递给print()方法进行打印操作。

方法重写和重载

Overload:

        方法重载是指在同一个类中,多个方法具有相同的方法名,但参数列表不同(参数的个数、类型、顺序)。

重载【方法名相同就行,返回值不管】方法名相同形参列表不同【顺序、个数、类型】。

class Overloading {
	public int test() {
		System.out.println("test1");
        return 1;
    }
    public void test(int a) {
        System.out.println("test2");
    }   
    //以下两个方法中参数类型的顺序不同
    public String test(int a,String s) {
        System.out.println("test3");
        return "test方法被重载第二次";
    }   
    public String test(String s,int a) {
        System.out.println("test4");
        return "test方法被重载第三次";
    }
}
public class Overload {
	public static void main(String[] args) {
		Overloading a=new Overloading();
        System.out.println(a.test());
        a.test(1);
        System.out.println(a.test(1,"test3"));
        System.out.println(a.test("test4",1));
	}
}

Override:

        方法重写是指在子类中重新定义父类中的方法。重写的方法具有相同的方法名、参数列表和返回类型

重写【啥都相同】方法名相同,参数列表相同,返回值相同

 举例1:

class Animal {
	public void move() {
		System.out.println("动物可以移动!");
	}
}
class Dogs extends Animal {
	public void move() {
		System.out.println("狗可以跑和跳!");
	}
}
public class Override {
	public static void main(String[] args) {
		Animal a=new Animal();
		Animal b=new Dogs();// 多态的方式
		a.move();
		b.move();
	}
}

动物可以移动!
狗可以跑和跳!

举例2

class AnimalPark {
	public void move() {
		System.out.println("动物可以移动!");
	}
}
class Dogs extends AnimalPark {
	public void move() {
		System.out.println("狗可以跑和跳!");
	}
	public void bark() {
		System.out.println("狗可以吠叫");
	}
}
public class Override {
	public static void main(String[] args) {
		AnimalPark a=new AnimalPark();
		AnimalPark b=new Dogs();
		a.move();
		b.move();
		//b.bark();此行代码会报错!!!
	}
}

该程序将抛出一个编译错误,因为 b 的引用类型 AnimalPark 中没有 bark 方法。 

        重写是子类对父类的方法进行重新编写, 返回值和形参列表都不能改变,方法体可以进行重写【可以理解为扩展】。即外壳不变,核心重写(语句体内容进行重写),当父类的行为不能满足子类需求的时候,子类就重写父类的方法。(子类可以既继承了父类的属性和方法,也可以有自己的独有属性和方法)

构造方法可以被重载但不能被重写无参构造和有参构造方法就是方法重载,这样在创建对象的过程中就可以有多种方式创建带参对象)。

为什么构造方法不能被重写?

        因为重写发生在父类和子类之间,要求方法名称相同,而构造方法的名称是和类名相同的,而子类类名不会和父类类名相同,所以不可以被重写。

普通类和抽象类的异同?

普通类:可以实例化对象(与抽象类最大的区别

抽象类(abstract):

        抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

抽象类的最大作用就是为了被子类继承【就是一些方法只想让子类进行执行】。

  1. final修饰的类,为最终类,该类不能被继承。如String 类

  2. final修饰的方法可以被继承和重载但不能被重写【final修饰了就不可变了】

  3. final修饰的变量不能被修改,是个常量【不可变】

为什么要使用抽象类?

        1.一些特定的工作不需要父类完成,而该由子类进行完成;

        2.将一些公共的特征和行为抽象成一个类,从而实现代码的复用。例如,在设计一个动物类时,可以将所有动物的共同特征抽象成为一个抽象类,将其它具体的动物类都继承该类,并实现自己的特有行为,如狗、猫、鸟等。

        3.实现功能的扩展,当子类继承抽象类的同时,可以对抽象类中抽象方法进行重写,从而实现自己独有的功能。

        抽象类:没有具体实现细节的方法, 我们可以把它设计成一个抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)。

public、protected,default和private的区别是什么?

  1. public 使用 public 修饰的成员(类、字段、方法等)可以在任何地方访问,无论是在同一包内还是在不同包内。它具有最广泛的可见性。

  2. protected 使用 protected 修饰的成员对于同一包内的类和不同包内的子类是可见的。在不同包内的非子类类是不可见的。

  3. default(package-private): 如果没有指定任何访问修饰符,则默认访问修饰符是 default(也称为 package-private)。使用 default 修饰的成员对于同一包内的类是可见的,但对于不同包内的类是不可见的。

  4. private 使用 private 修饰的成员只能在同一类内部访问,对于其他类都不可见。

这些修饰符的作用范围如下:

  • public:全局可见。
  • protected:包内可见,子类可见。
  • default:包内可见。
  • private:类内部可见。
class AccessModifiersTest {
    public int publicField = 1;
    protected int protectedField = 2;
    int defaultField = 3;
    private int privateField = 4;
    
    public void publicMethod() {
        System.out.println("publicMethod called");
    }
    
    protected void protectedMethod() {
        System.out.println("protectedMethod called");
    }
    
    void defaultMethod() {
        System.out.println("defaultMethod called");
    }
    
    private void privateMethod() {
        System.out.println("privateMethod called");
    }
}

public class Main {
    public static void main(String[] args) {
        AccessModifiersTest test = new AccessModifiersTest();
        
        System.out.println("Public field: " + test.publicField);
        System.out.println("Protected field: " + test.protectedField);
        System.out.println("Default field: " + test.defaultField);
        // privateField is not accessible here
        
        test.publicMethod();
        test.protectedMethod();
        test.defaultMethod();
        // privateMethod is not accessible here
    }
}

接口和抽象类的异同

相同:

1,都不能被实例化,(只能被继承或者被实现)。

2,都可以包含抽象方法。

不同:

1,关键字不同:

        1.1继承抽象类的关键字为extends,实现接口的关键字是implements;

        1.2定义抽象类的关键字是abstract,定义接口的关键字是interface;

2,权限修饰不同:

抽象方法可以有public protected和dafault这些修饰符(缺省情况默认为public)。

而接口的方法只能是public进行修饰。

3,构造方法:

抽象类可以有构造方法;

接口不能有构造方法;

4,方法不同:
抽象类中可以有抽象方法也可以有普通方法;接口中只能有抽象方法

注意:在JDK1.8过后,开始允许接口中有非抽象方法,但需要使用default关键字进行修饰;

5,影响:

抽象类中增加方法可以不影响子类;(抽象类中的普通方法可以继承也可以不继承,除非是抽象方法,需要重写。)

接口中增加方法一定会影响子类;(接口中的抽象方法,子类必须进行重写。)

6,字段:

抽象类中可以定义实例字段和静态字段。,接口只能定义常量字段,不能定义实例字段。字段默认为 public static final。

7,抽象类只能单继承,接口可以多实现。

下面是抽象类和接口的实例,便于更好的理解之间的区别:

抽象类示例:

abstract class Shape {
    String name; // 实例字段
    static int count; // 静态字段

    abstract void draw(); // 抽象方法,子类必须实现
    
    //构造方法
    Shape(String name) {
        this.name = name;
    }
    void displayInfo() { // 具体方法,子类可以直接继承
        System.out.println("This is a " + name + ".");
    }

    Shape() {
        count++;
    }

    static void showCount() {
        System.out.println("Total shapes: " + count);
    }
}

class Circle extends Shape {
    Circle() {
        name = "circle";
    }

    @Override
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Rectangle extends Shape {
    Rectangle() {
        name = "rectangle";
    }

    @Override
    void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

接口示例:

interface Drawable {
    String name = ""; // 常量字段

    void draw(); // 抽象方法,实现类必须实现(默认public static final修饰)
    default void displayInfo() { // 默认方法,实现类可以直接继承或重写
        System.out.println("This is a " + name + ".");
    }

    static void showInfo() {
        System.out.println("This is a drawable object.");
    }
}

class Circle implements Drawable {
    Circle() {
        name = "circle";
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Rectangle implements Drawable {
    Rectangle() {
        name = "rectangle";
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

IO流

你知道BIO,NIO,AIO么?

IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。

BIO (Blocking I/O)

        同步(操作)-阻塞I/O(线程)模式以流的方式从一个连接中读取数据数据读取写入必须等待另一个线程完成。适用于单线程服务器或少量连接的服务器,性能较差

NIO (New I/O)

        同步-非阻塞模式,它通过selector监听多个IO通道,从而实现异步IO的操作,不会阻塞线程,适用于高并发,连接数多的场景。需要使用ByteBuffer来进行数据读写。相对于BIO,代码编写复杂,但是性能比较高。

AIO ( Asynchronous I/O)

        异步-非阻塞I/O 模式,相比于BIO和NIO,AIO进一步将IO处理的事件交给系统内核进行处理,当系统内核完成IO操作后,才会通知应用程序。使用于高并发,连接数目多且IO处理耗时比较长的场景。需要使用到CompletionHandler回调函数来实现数据读写,代码更复杂,效率更高。

异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

Java 中四大基础流

字符流:

Reader(字符输入流-读取数据)

Writer(字符输出流-写数据)

字节流:

InputStream(字节输入流-读取数据)

OutputStream(字节输出流-写数据)

读文本用什么流,读图片用什么流

读文本使用使用字符输入流Reader,也可以用字节输入流,但是读取的时候要将字符转换成字节,效率比较低,所以最好使用字符输入流进行读取。也可以使用缓冲字符输入流BufferReader将字符输入流包装起来,一次读取一个字符数组,提高读取效率。

读取图片使用字节输入流InputStream,字节输入流可以读取任何类型,会将所有类型转换成二进制进行读取,可以使用字节缓冲输入流BufferInputStream包装字节输入流,一次读取一个字节数组提高读取效率。

字符流写数据会先放在缓冲区只有调用flush或者close关流【close方法中包含了flush】,才会将缓冲区的数据写出去。

而字节流是直接输出的,没有关流(close)之前已经将数据读取出去了,调用close方法只是为了释放资源,避免一直占用。

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

InputStream(字节输入流) OutputStream(字节输出流)

字节流的按照字节进行读取数据一次读取一个字节(byte),相当于8位二进制,适用于读取任何类型的文件

字节流可分为字节输入流和字节输出流,为了提高读取效率,又出现了缓冲字节输入流和缓冲字节输出流(一次读取一个字节数组,从而提高读取效率)。

Reader(字符输入流) Writer(字节输出流)

字符流的按照字符进行读取数据,一次读取一个字符(char),相当于16位二进制,适用于文本类型。

字符流可分为字符输入流和字符输出流,为了提高读取效率,又出现了缓冲字符输入流和缓冲字符输出流(一次读取一个字符数组,从而提高读取效率)

BufferedInputStream(缓冲字节输入流) 用到什么设计模式

主要运用了俩个设计模式,适配器和装饰者模式

装饰器模式(Decorator )

允许向一个现有的对象添加新的增强功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装

适配器模式(Adapter)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能【USB接口】

常用设计模式+代理模式【Design Pattern】【我终于懂设计模式了】_GuGuBirdXXXX的博客-CSDN博客

怎么实现一张图片拷贝

需要一个FileInputStream文件字节输入流指向读取的文件,然后把它包装BufferInputStream缓冲字节输入流【创建一个内部缓冲区数组】,使用BufferInputStreamread方法去读byte[]字节数组,然后创建一个FileOutputStream指向输出文件写数据,然后把它包装BufferOutputStream,使用BufferOutputStream的write方法写byte[]到另外一个文件

怎么实现文本拷贝

文件拷贝思路一样,只不过读的时候需要使用BufferedReaderFileReader,使用readline方法来读 , 写的时候需要BufferedWriterFileWriter,用wite来写

 

深拷贝和浅拷贝?

深拷贝和浅拷贝都是对数据进行复制操作的方式,两者的区别在于复制的程度不同。

浅拷贝只是复制对象的引用,而不是对象本身。也就是说,当我们对原对象进行浅拷贝时,得到的新对象是和原对象共享同一块内存地址,因为对新对象进行修改时,会导致原对象也随之发生改变。

深拷贝就与浅拷贝截然不同,会将原对象及其子对象的所有内容复制到新对象中,得到完全独立的对象,与原对象保持完全独立,不会互相影响

它们通过实现Cloneable接口,然后调用clone方法进行拷贝【是直接复制一个对象,而不是引用地址】。

创建对象的方式有哪些?

1.使用new关键字创建对象

使用new关键字可以直接在内存中分配空间创建对象。例如:

// 创建一个Person对象
Person person = new Person();

2.使用Class类的newInstance()方法创建对象

Class类是JVM在运行时自动加载的,通过Class类的newInstance()方法可以创建该类的对象。例如:

// 获取Person类的Class对象
Class<Person> clazz = Person.class;
// 创建一个Person对象
Person person = clazz.newInstance();

需要注意的是,该方式只适用于具有默认构造函数的类。

3.使用构造方法创建对象

对于一个类,可以定义多个构造函数来创建对象。例如:

// 创建一个Person对象,指定name和age
Person person = new Person("Tom", 18);

4.使用反射创建对象

Java中的反射机制可以在运行时获取类的信息,并进行实例化操作。例如:

// 获取Person类的Class对象
Class<Person> clazz = Person.class;
// 创建一个Person对象,通过无参构造函数
Person person = clazz.newInstance();
// 获取name属性的Field对象,并设置值
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "Tom");
// 获取age属性的Field对象,并设置值
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(person, 18);

需要注意的是,反射操作会影响性能,因此应尽可能避免不必要的反射操作。

5.clone的方式

1.在需要复制的类中实现Cloneable接口

public class Person implements Cloneable {
    // ...
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

2.在需要创建对象的地方,通过调用clone()方法复制对象。

// 创建一个Person对象
Person person = new Person("Tom", 18);
// 复制一个新的Person对象
Person newPerson = (Person) person.clone();

需要注意的是,clone()方法是浅拷贝方式,因此如果原对象中的某个属性是引用类型,那么在新对象中仍然是指向同一个对象,并没有复制一份新的。如果需要深拷贝,需要在clone()方法中进行相应的处理。

Java8新特性常用API

JAVA8中stream流常用的方法(一篇全搞定)_MXin5的博客-CSDN博客

Java length() 方法、length 属性和 size() 方法有什么区别?

1、length() 方法: 是针对字符串来说的,要求一个字符串的长度就要用到它的 length() 方法。

2、length 属性: 是针对 Java 中的数组来说的,要求数组的长度可以用其 length 属性。

3、size() 方法: 是针对泛型集合来说的,如果想看这个泛型有多少个元素,就调用此方法来查看。

// 数组
String[] array = { "First", "Second", "Third" };
// 字符串
String a = "HelloWorld";
// List
List<String> list = new ArrayList<>();
list.add(a);
// 打印log
Log.i(TAG, "数组的长度: " + array.length);
Log.i(TAG, "字符串的长度: " + a.length());
Log.i(TAG, "List中元素个数: " + list.size());
数组的长度: 3
字符串的长度: 10
List中元素个数: 1

数组和集合的区别?

       一、数组声明了它容纳的元素的类型,而集合不声明(Object)。

       二、数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。

       三、数组不论是效率还是类型检查都是最好的。

1.数组是大小固定的,一旦创建无法扩容;集合大小不固定,

2.数组的存放的类型只能是一种,集合存放的类型可以不是一种(不加泛型时添加的类型是Object);

3.数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查(不懂),都是最快的.

int[] m = { 1, 2, 3 };// int数组

String[] strings = { "aaa", "bbb" };// String数组

List<String> list = new ArrayList<String>(); // 指定String类型的集合

List<Integer> lists = new ArrayList<Integer>(); // 指定Integer类型的集合

List<Map<String, Object>> list2 = new ArrayList<Map<String,Object>>(); // 执行的Map集合

List<City> listcity = new ArrayList<City>(); // 指定对象的集合

String常用API

(一)常见String类的获取功能

1.length()方法  获取字符串长度

    String str = "abcdef";
    System.out.println(str.length());//输出结果:6

2.charAt(int index)方法  传递一个下标参数,返回字符串对应位置的字符

    String str = "abc";
    System.out.println(str.charAt(1));//输出结果:b

​​​​3.indexOf()方法  传递某个字符,返回在字符串中的第一个位置

    String str = "abcabc";
    System.out.println(str.indexOf('a'));//输出结果:0

4.subString(int start)方法  默认是取到字符串末尾

    String str = "abcdef";
    System.out.println(str.subString(2));//输出结果:cdef

5.subString(int start,int end)方法 注:范围左闭右开,不包含下标为end的那位

    String str = "abcdefgh";
    System.out.println(str.subString(2,5));//输出结果:cde

(二)常见String类的判断功能

1.equals()方法 判断字符串内容是否相同,区分大小写。

  String str1 = "abc";
    String str2 = "ABC";
    System.out.println(str1.equals(str2));//输出结果:false

2.contains()方法 判断该字符串是否包含传递过来的字符串。

 String str1 = "我是猪";
    String str2 = "我是";
    String str3 = "是猪";
    System.out.println(str1.contains(str2));//输出结果:true
    System.out.println(str1.contains(str3));//输出结果:true

3.startsWith()方法 判断字符串是否以传进来的字符串开头。

endsWith()方法 判断字符串是否以传进来的字符串结尾。

  String str1 = " 我是猪";
    String str2 = " ";
    System.out.println(str1.startsWith(str2));//true
    System.out.printl(str1.endsWith(str2));//false

4.isEmpty()方法 判断字符串是否为空。

  String str = "";
    String str1 = "a";
    System.out.println(str.isEmpty());//true
    System.out.println(str1.isEmpty());//false

(三)常见String类的转换功能

1.getBytes()方法 返回值类型 byte[]

    使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

2.toCharArray()方法 将String类型的字符串转化为char字符数组

 Strint str = "我是你爹";
    char[] c = str.toCharArray();
    System.out.println(c[0]);//输出结果:我
    System.out.println(c[1]);//输出结果:是
    System.out.println(c[2]);//输出结果:你
    System.out.println(c[3]);//输出结果:爹

3.valueOf(char[] data)方法 将传递的字符数组,组合成字符串。

  char[] c = {'a','b','c','d'};
    String str = new String();
    str = str.valueOf(c);
    System.out.println(str);//输出结果:abcd

4.toLowerCase()方法 把字符串转成小写toUpperCase()方法 把字符串转成大写

5.comcat()方法 字符串拼接

    String str1 = "abc";
    String str2 = "efg";
    String str3 = str1.concat(str2);
    System.out.println(str3);//输出结果:abcdef

(四)常见String类的其他常用功能

1.replace()方法 用传递的字符或字符串,替代指定的字符或者字符串。

2.trim()方法 去除两端空格

 String str = "  哈哈哈  ";
    System.out.println(str);//输出结果:  哈哈哈  ;
    str = str.trim();
    System.out.println(str);//输出结果:哈哈哈;

3.compareTo()方法

   //源码:
    public int compareTo(String anotherString) {
            int len1 = value.length;
            int len2 = anotherString.value.length;
            int lim = Math.min(len1, len2);
            char v1[] = value;
            char v2[] = anotherString.value;
     
            int k = 0;
            while (k < lim) {
                char c1 = v1[k];
                char c2 = v2[k];
                if (c1 != c2) {
                    return c1 - c2;
                }
                k++;
            }
            return len1 - len2;
    }

    解析:如果字符串与传递字符串的长度不等,那么返回就是两个字符串的长度差值;如果两个字符串长度相等,那么返回的就是,两个字符串长度最小值位置的字符ASCII码的差值。

  String str1 = "abc";
    String str2 = "ab";
    String str3 = "abd";
    String str4 = "abcdef";
    int a = str1.compareTo(str2);
    System.out.println(a);//输出结果:1
    int b = str1.compareTo(str3);
    System.out.println(b);//输出结果:-1
    int c = str1.compareTo(str4);
    System.out.println(c);//输出结果:-3


Object常用的API

在 Java 中,Object 是所有类的根类,它包含了一些常用的方法。以下是一些 Object 类的常用方法:

toString():返回对象的字符串表示。默认实现返回对象的类名和散列码。

equals(Object obj):比较对象是否相等。默认实现比较引用是否相同,可以根据需要重写来实现自定义的相等逻辑。

hashCode():返回对象的散列码。默认实现返回对象的内存地址,一般需要和 equals 方法一起重写。

getClass():返回对象的运行时类。

notify() 和 notifyAll():用于线程通信,在等待中的线程被唤醒。

wait() 和 wait(long timeout):使当前线程进入等待状态,直到其他线程调用 notify 或 notifyAll 唤醒它。

wait(long timeout, int nanos):与上面的 wait 方法类似,但可以指定纳秒级别的等待时间。

finalize():垃圾回收器调用的方法,用于释放对象资源。不推荐使用,因为在 Java 9 中已被弃用。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mxin5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值