访问修饰符public,private,protected,以及不写(默认)时的区别?
Java中,可以使用访问控制符来保护对类,变量,方法和构造方法的访问。
Java支持4种不同的访问权限。
default(即默认,什么也不写):在同一包内可见,不使用任何修饰符。
可以修饰在类,接口,变量,方法。
private:在同一类内可见。
可以修饰变量,方法。
注意,不能修饰类(外部类)
public:对所有类可见。
可以修饰类,接口,变量,方法
protected:对同一包内的类和所有子类可见。
this关键字有什么作用?
this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
this的用法在Java中大体可以分为3种:
1.普通的直接引用,this相当于是指向当前对象本身
2.形参与成员变量名重名,用this来区分
3.引用本类的构造方法
抽象类和接口有什么区别?
一个类只能继承一个抽象类:但一个类可以实现多个接口。
所以我们在新建线程类的时候一般推荐使用实现Runnable接口的方式,这样线程类还可以继承其他类,而不是单单的Thread类。
抽象类符合is-a的关系,而接口更像是has-a的关系,比如说一个类可以序列化的时候,它只需要实现Serializable接口就可以了,不需要去继承一个序列化类。
抽象类更多地是用来为多个相关的类提供一个共同的基础框架,包括状态的初始化,而接口则是定义一套行为标准,让不同的类可以实现同一接口,提供行为的多样化实现。
抽象类可以定义构造方法吗?
可以,抽象类可以有构造方法。
abstract class Animal{
protected String name;
public Animal(String name){
this.name=name;
}
public abstract void makeSound();
}
public class Dog extends Animal{
private int age;
public Dog(String name,int age){
super(name);//调用抽象类的构造函数
this.age=age;
}
@Override
public void makeSound(){
System.out.println(name+“ says: Bark”);
}
}
接口可以定义构造方法吗?
不能,接口主要用于定义一组方法规范,没有具体的实现细节
接口可以多继承吗?
接口可以多继承,一个接口可以继承多个接口,使用逗号分隔。
interface InterfaceA{
void mathodA();
}
interface InterfaceB{
void mathodB();
}
interface InterfaceC extends InterfaceA,InterfaceB{
void methodC();
}
class MyClass implements InterfaceC{
public void methodA(){
System.out.printlnn("Method A")
}
public void methodB(){
System.out.println("Method B");
}
public void methodC(){
System.out.println("Method C");
}
public statci void main(String[] args){
MyClass myClass = new MyClass();
myClass.methodA();
myClass.methodB();
myClass.methodC();
}
}
在上面的例子中,InterfaceA和InterfaceB是两个独立的接口
InterfaceC继承了InterfaceA和InterfaceB,并且定义了自己的方法methodC
MyClass实现了InterfaceC,因此需要实现InterfaceA和InterfaceB中的方法methodA和methodB,以及IntrerfaceC中的方法methodC
继承和抽象的区别?
继承是一种允许子类继承父类属性和方法的机制。
通过继承,子类1可以重用父类的代码
抽象是一种隐藏复杂性和只显示必要必要部分的技术。
在面向对象编程中,抽象可以通过抽象类和接口实现。
抽象类和普通类的区别?
抽象类使用abstract关键字定义,不能被实例化,只能作为其他类的父类。
普通类没有abstract关键字,可以直接实例化。
抽象类可以包含抽象方法和非抽象方法。
抽象方法没有方法体,必须由子类实现。
普通类只能包含非抽象方法。
abstract class Animal{
//抽象方法
public abstract void makeSound();
//非抽象方法
public void eat(){
System.out.prinltln("This animal is eating.");
}
}
class Dog extends Animal{
//实现抽象方法
@Override
public void makeSound(){
System.out.println("Woof");
}
}
public class Test{
public static void main(String[] args){
Dog dog=new Dog();
dog.makeSound();//输出“Woof”
dog.eat();//输出“This animal is eating.”
}
}
成员变量与局部变量的区别有那些?
从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;
成员变量可以被public,private,static等修饰,而局部变量不能被访问控制修饰符及static所修饰;
但是,成员变量和局部变量都能被final所修饰。
从变量的内存中的存储方式来看:如果成员变量使用static修饰的,那么这个成员变量是属于类的,如果没有用static修饰,这个成员变量是属性实例的。
对象存于堆内存,如果局部变量为基本数据类型,那么存储在栈内存,如果为引用数据类型,那存放的是指向堆内存对象的引用或者是指向常量池中的地址。
从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失
成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被final修饰的成员变量也必须显示地赋值),而局部变量则不会自动赋值。
静态变量和实例变量的区别?静态方法,实例方法呢?
静态变量和实例变量的区别?
静态变量:是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。
实例变量:必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。
静态变量可以实现多个对象共享内存。
类似地。
静态方法:static修饰的方法,也被称为类方法。
在外部调用静态方法时,可以使用“类名.方法名”的方式,也可以使用“对象名,方法名”的方式。
静态方法里不能访问类的非静态成员变量和方法。
实例方法:依存于类的实例,需要使用“对象名.方法名”的方式调用;可以访问类的所有成员变量和方法。
final关键字有什么用?
当final修饰一个类时,表明这个类不能被继承。
比如,String类,Integer类和其他包装类都是final修饰的。
当final修饰一个方法时,表明这个方法不能被重写(Override)。
也就是说,如果一个类继承了某个类,并且想要改变父类中被final修饰的方法的行为,是不被允许的。
当final修饰一个变量时,表明这个变量的值一但被初始化就不能被修改。
如果是基本数据类型的变量,其数组一旦在初始之后就不能更改;
如果是引用类型的变量,在对其初始化之后就不能再让其指向另一个对象。
但是引用指向的对象内容是可以改变。
final,finally,finalize的区别?
final是一个修饰符,可以修饰类,方法和变量。
当final修饰一个类时,表明这个类不能被继承;
当final修饰一个方法时,表明这个方法不能被重写;
当final修饰一个变量时,表明这个变量是常量,一旦赋值后,就不能再被修改了。
finally是Java中异常处理的一部分,用来创建try块后面的finally.
无论try块中的代码是否抛出异常,finally块中的代码总是会被执行。
通常,finally块被用来释放资源,如关闭文件,数据库连接等。
finalize是Object类的一个方法,用于在垃圾回收器对象从内存中清除出去之前做一些必要的清理工作。
这个方法在垃圾回收器准备释放对象占用的内存之前被自动调用。
我们不能显示地调用finalize方法,因为它总是由垃圾回收器在适当的时间自动调用。
==和equals的区别?
在Java中,==操作符和equals()方法用于比较两个对象;
==用于比较两个对象的引用,即它们是否指向同一个对象实例。
如果两个变量引用同一个对象实例,==返回true,否则返回false.
对于基本数据类型(如int,double,char等),==比较的是值是否相等。
equals()方法:用于比较两个对象的内容是否相等。
默认情况下,equals()方法的行为与==相同,即比较对象引用,如在超类Object中:
public boolean equals(Object obj){
return (this==obj);
}
然而,equals()方法通常被各种类重写。
例如,String类重写了equals()方法,以便它可以比较两个字符串的字符内容是否完全一样。
举个例子:
String a=new String("沉默王二”);
String b=new String("沉默王二”);
//使用==比较
System.out.println(a==b);//输出false,因为a和b引用不同的对象
//使用equals()比较
System.out.println(a.equals(b));//输出true,因为a和b的内容相同
hashCode与equals?
这到题也面试常问得——“你重写过hashcode和equal么,为什么重写equals时必须重写hashCode方法?”
什么是hashCode方法?
hashCode()方法的作用是获取哈希码,它会返回一个int整数,定义在Object类中,是一个本地方法。
public native int hashCode();
为什么要有hashCode方法?
hashCode方法主要用来获取对象的哈希码,哈希码是由对象的内存地址或者对象的属性计算出来的,它是一个int类型的整数,通常是不会重复的,因此可以用来作为键值对的键,以提高查询效率。
例如HashMao中的key就是通过hashCode来实现的,通过调用hashCode方法获取键的哈希码,并将其与右移16位的哈希码进行异或运算。
static final int hash(Object key){
int h;
return (key == null)? 0:(h=key.hashCode())^(h>>>16);
}
为什么重写equals时必须重写hashCode方法?
维护equals()和hashCode()之间的一致性是至关重要的,因为基于哈希的集合类(如HashSet,HashMap,Hashtable等)依赖于这一点来正确存储和检索对象。
具体地说,这些集合通过对象的哈希码将其存储在不同的“桶”中(底层数据结构是数组,哈希码用来确定下标),当查找对象时,他们使用哈希码确定在那个桶中搜索,然后通过equals()方法在桶中找到正确的对象。
如果重写了equals()方法而没有重写hashCode()方法,那么被认为相等的对象可能会有不同的哈希码,从而导致无法在集合中正确处理这些对象。
为什么两个对象有相同的hashcode值,他们也不一定相等?
这主要是由于哈希码(hashCode)的本质和目的所决定的。
哈希码是通过哈希函数将对象中映射成一个整数值,其主要目的是在哈希表中快速定位对象的存储位置。
由于哈希函数将一个较大的输入域映射到一个较小的输出域,不同的输入值(即不同的对象)可能会产生相同的输出域(即相同的哈希码)
这种情况被称为哈希冲突。
当两个不相等的对象发生哈希冲突时,他们会有相同的hashCode。
为了解决哈希冲突的问题,哈希表在处理键时,不仅会比较对象的哈希码,还会使用equals方法来检查键对象是否真正相等。
如果两个对象的哈希码相等,但通过equals方法比较结果为false,那么这两个对象就不被视为相等。
if(p.hash==hash &&
((k=p.key)==key ||(key !=null &&key.equals(k)))
e = p;
java是值传递,还是引用传递?
Java是值传递,不用引用传递。
当一个对象被作为参数传递到方法中去时,参数的值就是该对象的引用。
引用的值是对象在堆在的地址。
对象是存储在堆中的,所以传递对象的时候,可以理解为把变量存储的对象地址给传递过去。
引用类型的变量有什么特点?
引用类型的变量存储的是对象的地址,而不是对象本身。
因此,引用类型的变量在传递时,传递的是对象的地址,也就是说,传递的是引用的值。
说说深拷贝和浅拷贝?
在Java中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种拷贝对象的方式,它们在拷贝对象的方式上有很大不同。
浅拷贝会创建一个新对象,但这个新对象的属性(字段)和原对象的属性完全相同。
如果属性是基本数据类型,拷贝的是基本数据类型的值;如果属性是引用类型,拷贝的是引用地址,因此新旧对象共享同一个引用对象。
浅拷贝的实现方式为:实现Cloneable接口并重写clone()方法
class Person implements Cloneable{
String name;
int age;
Address address;
public Person(String name,int age,Address address){
this,name=name;
this.age=age;
this.address=address;
}
@Override
protected Object clone() throws CloneNotSupportedException{
return super.clone();
}}
class Address{
String city;
public Address(String city){
this.city=city;
}}
public class Main{
public static void main(String[] args) throws CloneNotSupportedException{
Address address =new Address("河南省洛阳市”);
Persion persion1=new Persion(“沉默王二”,18,address);
Persion persion2=(Person) person1.clone();
System.out.println(person1.address == persion2.address);//true
}}
深拷贝也会创建一个新对象,但会递归地复制所以的引用对象,确保新对象和原对象完全独立。
新对象与原对象的任何更改都不会相互影响。
深拷贝的实现方式有:手动复杂所有的引用对象,或者使用序列化或者反序列化。
1.手动拷贝
class Person{
String name;
int age;
Address address;
public Person(String name,int age,Address address){
this.name=name;
this.age=age;
this.address=address;
}
public Person(Person person){
this.name=person.name;
this.age=person.age;
this.address=new Address(person.address.city);
}
}
class Address{
String city;
public Address(String city){
this.city=city;
}
}
public class Main{
public static void main(String[] args){
Address address =new Address("河南洛阳市”);
Person person1= new Person("沉默王二”,18,address);
Person person2=new Person(person1);
System.out.println(person1.address == person2.address);//false
}
}
import java.io.*;
class Person implements Serializable{
String name;
int age;
Address address;
public Person(String name,int age,Address address){
this.name = name;
this.age=age;
this.address =address;
}
public Person deepClone() throws IOException,ClassNotFoundException{
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis =new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (Person)ois.readObject();
}
}
class Address implements Serializable{
String city;
public Address(String city){
this.city = city;
}
}
public class Main{
public static void main(String[] args) throws IOExceptionn,ClassNotFoundException{
Address address = new Address("河南省洛阳市”);
Person person1=new Person("沉默王二,18,address);
Person person2 = person1.deepClone();
System.out.println(person1.address == person2.address);//false
}}
Java创建对象有哪几种方式?
Java有四种创建对象的方式:
1。new关键字创建,这是最常见和直接的方式,通过调用类的构造方法来创建对象
Person person = new Person();
2.反射机制创建,反射机制允许在运行时创建对象,并且可以访问类的私有成员,在框架和工具这比较常见。
Class clazz =Class.forName(“Person”);
Person person = (Person) clazz.newInstance();
3.clone拷贝创建,通过clone方法创建对象,需要实现Cloneable接口并重写clone方法
Person person = new Person();
Person person2 = (Person) person.clone();
4.序列化机制创建,通过序列化将对象转换为字节流,在通过反序列化从字节流中恢复对象。
需要实现Serializable接口
Person person = new Person();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt");
oos.writeObject(person);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));
Person person2 =(Person) ois.readObject();
new子类的时候,子类和父类静态代码块,构造方法的执行顺序
在Java中,当创建一个子类对象时,子类和父类的静态代码块,构造方法的执行顺序遵循一定的规则。
这些规则主要包括以下几个步骤:
1.首先执行父类的静态代码块(仅在类第第一次加载时执行)
2.接着执行子类的静态代码块(仅在类第一次加载时执行
3.再执行父类的构造方法;
4.最后执行子类的构造方法;
Class Parent{
//父类静态代码块
static{
System.out.println("父类静态代码块”);
}
//父类构造方法
public Parent(){
System.out.println("父类构造方法”);
}
}
class Child extends Parent{
//子类静态代码块
static{
System.out.println("子类静态代码块”);
}
//子类构造
public Child(){
System.out.printLn("子类构造方法”);
}
}
public class Main{
public static void main(String[] args){
new Child();
}
}
执行上述代码时,输出结果如下:
父类静态代码块
子类静态代码块
父类构造方法
子类构造方法
静态代码块:在类加载时执行,仅执行一次,按父类-子类的顺序执行
构造方法:在每次创建对象时执行,按父类-子类的顺序执行,先初始化块后构造方法。