目录
String和StringBuilder和StringBuffer区别?
String a = "aa" 和 String a = new String("aa") 创建字符串的区别
为什么重写了hashcode方法还需要重写equals方法?
final 和 finally 和 finalize 的区别
public、protected,default和private的区别是什么?
BufferedInputStream(缓冲字节输入流) 用到什么设计模式
2.使用Class类的newInstance()方法创建对象
Java length() 方法、length 属性和 size() 方法有什么区别?
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的深入对比:
-
两个通过new生成的Integer对象,由于在堆中地址不同,所以永远不相等【因为是final修饰的,每次都会创建一个新的对象。】
-
int和Integer比较时,只要数值相等,结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int
-
通过new生成的Integer对象和非通过new生成的Integer对象相比较时,由于前者存放在堆内存中【堆内存存储的是对象】,后者存放在Java常量池【基本数据的对象和数组等】中,所以永远不相等
-
两个非通过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关键字的作用?
final修饰的类,为最终类,该类不能被继承。如String 类
final修饰的方法可以被继承和重载,但不能被重写
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的安装目录下有一个jre【java runtime environment】目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm【java virtual machine】,lib中则是jvm工作所需要的核心类库,而jvm和 lib合起来就称为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()
方法。Rectangle
和Circle
继承自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):
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
抽象类的最大作用就是为了被子类继承【就是一些方法只想让子类进行执行】。
final修饰的类,为最终类,该类不能被继承。如String 类
final修饰的方法可以被继承和重载,但不能被重写【final修饰了就不可变了】
final修饰的变量不能被修改,是个常量【不可变】
为什么要使用抽象类?
1.一些特定的工作不需要父类完成,而该由子类进行完成;
2.将一些公共的特征和行为抽象成一个类,从而实现代码的复用。例如,在设计一个动物类时,可以将所有动物的共同特征抽象成为一个抽象类,将其它具体的动物类都继承该类,并实现自己的特有行为,如狗、猫、鸟等。
3.实现功能的扩展,当子类继承抽象类的同时,可以对抽象类中抽象方法进行重写,从而实现自己独有的功能。
抽象类:没有具体实现细节的方法, 我们可以把它设计成一个抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)。
public、protected,default和private的区别是什么?
-
public
: 使用public
修饰的成员(类、字段、方法等)可以在任何地方访问,无论是在同一包内还是在不同包内。它具有最广泛的可见性。 -
protected
: 使用protected
修饰的成员对于同一包内的类和不同包内的子类是可见的。在不同包内的非子类类是不可见的。 -
default
(package-private): 如果没有指定任何访问修饰符,则默认访问修饰符是default
(也称为 package-private)。使用default
修饰的成员对于同一包内的类是可见的,但对于不同包内的类是不可见的。 -
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缓冲字节输入流【创建一个内部缓冲区数组】,使用BufferInputStream的read方法去读byte[]字节数组,然后创建一个FileOutputStream指向输出文件写数据,然后把它包装到BufferOutputStream,使用BufferOutputStream的write方法写byte[]到另外一个文件
怎么实现文本拷贝
和文件拷贝思路一样,只不过读的时候需要使用BufferedReader和FileReader,使用readline方法来读 , 写的时候需要BufferedWriter和 FileWriter,用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 中已被弃用。