Java基础

Java基础

面向对象特性

封装:
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
继承:
继承实现了 IS-A 关系,例如Cat和Animal就是一种 IS-A 关系,因此Cat可以继承自Animal,从而获得Animal非private的属性和方法。继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。Cat可以当做Animal来使用,也就是说可以使用Animal引用Cat对象。父类引用指向子类对象称为向上转型。
多态:
多态分为编译时多态和运行时多态:
编译时多态主要指方法的重载(OverLoad)
运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。
运行时多态有三个条件:继承、覆盖(重写(OverRide))、向上转型。

重载(Overload)

重载发生在本类,方法名相同,参数列表不同,与返回值无关,只和方法名,参数列表,参数的类型有关.
重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。
(1):方法名必须相同
(2):方法的参数列表一定不一样。
(3):访问修饰符和返回值类型可以相同也可以不同。
其实简单而言:重载就是对于不同的情况写不同的方法。 比如,同一个类中,写不同的构造函数用于初始化不同的参数。

重写(Overriding)

重写发生在父类子类之间,比如所有类都是继承与Object类的,Object类中本身就有equals, hashcode,toString方法等.在任意子类中定义了重名和同样的参数列表就构成方法重写.
重写(override):一般都是表示子类和父类之间的关系,其主要的特征是:方法名相同,参数相同,但是具体的实现不同。
重写的特征:
(1):方法名必须相同,返回值类型必须相同
(2):参数列表必须相同
(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
(5):构造方法不能被重写,
简单而言:就是具体的实现类对于父类的该方法实现不满意,需要自己在写一个满足于自己要求的方法。

a = a + b 与 a += b 的区别

如果两个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。

byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok

(因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)

对equals()和hashCode()的理解

为什么在重写 equals 方法的时候需要重写hashcode方法?
因为有强制的规范指定需要同时重写hashcode与equals是方法,许多容器类,如HashMap、HashSet都依赖于hashcode与equals的规定。
有没有可能两个不相等的对象有相同的hashcode?
有可能,两个不相等的对象可能会有相同的hashcode值,这就是为什么在hashmap中会有冲突。相等hashcode值的规定只是说如果两个对象相等,必须有相同的hashcode值,但是没有关于不相等对象的任何规定。
两个相同的对象会有不同的hashcode吗?
不能,根据hashcode的规定,这是不可能的。

final、finalize 和 finally 的不同之处

final是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。
Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用finalize没有保证。
finally 是一个关键字,与try和catch一起用于异常的处理。finally块一定会被执行,无论在try块中是否有发生异常。

String、StringBuffer与StringBuilder的区别

第一点: 可变和适用范围。
String对象是不可变的,而StringBuffer和StringBuilder是可变字符序列。每次对String的操作相当于生成一个新的String对象,而对StringBuffer和StringBuilder的操作是对对象本身的操作,而不会生成新的对象,所以对于频繁改变内容的字符串避免使用String,因为频繁的生成对象将会对系统性能产生影响。
第二点: 线程安全。
String由于有final修饰,是immutable的,安全性是简单而纯粹的。StringBuilder和StringBuffer的区别在于StringBuilder不保证同步,也就是说如果需要线程安全需要使用StringBuffer,不需要同步的StringBuilder效率更高。

接口与抽象类的区别

1、一个子类只能继承一个抽象类, 但能实现多个接口。
2、抽象类可以有构造方法, 接口没有构造方法。
3、抽象类可以有普通成员变量, 接口没有普通成员变量。
4、抽象类和接口都可有静态成员变量, 抽象类中静态成员变量访问类型任意,接口只能public static final(默认)。
5、抽象类可以没有抽象方法, 抽象类可以有普通方法;接口在JDK8之前都是抽象方法,在JDK8可以有default方法,在JDK9中允许有私有普通方法。
6、抽象类可以有静态方法;接口在JDK8之前不能有静态方法,在JDK8中可以有静态方法,且只能被接口类直接调用(不能被实现类的对象调用)。
7、抽象类中的方法可以是public、protected; 接口方法在JDK8之前只有public abstract,在JDK8可以有default方法,在JDK9中允许有private方法。

this() & super()在构造方法中的区别

1、调用super()必须写在子类构造方法的第一行, 否则编译不通过。
2、super从子类调用父类构造, this在同一类中调用其他构造均需要放在第一行。
3、尽管可以用this调用一个构造器, 却不能调用2个。
4、this和super不能出现在同一个构造器中, 否则编译不通过。
5、this()、super()都指的对象,不可以在static环境中使用。
6、本质this指向本对象的指针。super是一个关键字。

Java移位运算符

“<<” :左移运算符,x << 1,相当于x乘以2(不溢出的情况下),低位补0。
“>>” :带符号右移,x >> 1,相当于x除以2,正数高位补0,负数高位补1。

为什么需要泛型?什么是泛型

适用于多种数据类型执行相同的代码。

private static int add(int a, int b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}
private static float add(float a, float b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}
private static double add(double a, double b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:

private static <T extends Number> double add(T a, T b) {
    System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
    return a.doubleValue() + b.doubleValue();
}

参考文章:https://pdai.tech/md/interview/x-interview.html#_1-2-%E6%B3%9B%E5%9E%8B

Java异常类层次结构

1、Throwable是Java语言中所有错误与异常的超类。
2、Error类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
3、Exception程序本身可以捕获并且可以处理的异常。Exception这种异常又分为两类:运行时异常和编译时异常。
运行时异常:
都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常)
是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
在这里插入图片描述

throw和throws的区别?

异常的申明(throws)
在Java中,当前执行的语句必属于某个方法,Java解释器调用main方法执行开始执行程序。若方法中存在检查异常,如果不对其捕获,那必须在方法头中显式声明该异常,以便于告知方法调用者此方法有异常,需要进行处理。 在方法中声明一个异常,方法头中使用关键字throws,后面接上要声明的异常。若声明多个异常,则使用逗号分割。如下所示:

public static void method() throws IOException, FileNotFoundException{
    //something statements
}

异常的抛出(throw)
如果代码可能会引发某种错误,可以创建一个合适的异常类实例并抛出它,这就是抛出异常。如下所示:

public static double method(int value) {
    if(value == 0) {
        throw new ArithmeticException("参数不能为0"); //抛出一个运行时异常
    }
    return 5.0 / value;
}

什么是反射

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

    @Test
    public void classTest() throws Exception {
        // 获取Class对象的三种方式
        logger.info("根据类名:  \t" + User.class);
        logger.info("根据对象:  \t" + new User().getClass());
        logger.info("根据全限定类名:\t" + Class.forName("com.test.User"));
        // 常用的方法
        logger.info("获取全限定类名:\t" + userClass.getName());
        logger.info("获取类名:\t" + userClass.getSimpleName());
        logger.info("实例化:\t" + userClass.newInstance());
    }

Constructor类及其用法:

// 得到某个类所有的构造方法:
Constructor[] constructors = Class.forName(java.lang.String).getConstructors();
// 得到某一个构造方法
Constructor constructor = Class.forName(java.lang.String).getConstructor(StringBuffer.class);
// 创建实例对象
String str = (String)constructor.newInstance(new StringBuffer(“abc”));
// Class.newInstance()方法
String obj = (String)Class.forName(java.lang.String).newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象;
package cn.sunft.day01.reflect;
import java.lang.reflect.Constructor;
public class ConstructorTest {
	public static void main(String[] args) throws Exception {
		//new String(new StringBuffer("abc"))
		//获取指定参数的构造器
		Constructor constructor1 = 
				String.class.getConstructor(StringBuffer.class);
		//构造的时候,需要传入一个StringBuffer的实例
		String str1 = (String) 
				constructor1.newInstance(new StringBuffer("abc"));
		System.out.println(str1.charAt(2));//c
	}
}

Method类及其用法

// 返回一个数组,该数组包含反映该类对象所表示的类或接口的所有声明方法的方法对象,包括public、protected、default(package)access和private方法,但不包括继承的方法。
public Method[] getDeclaredMethods() throws SecurityException
// 返回一个数组,其中包含反映该类对象所表示的类或接口的所有公共方法的方法对象,包括由类或接口声明的方法对象以及从超类和超接口继承的方法对象
public Method[] getMethods() throws SecurityException
// 获取一个指定名称和形参类型的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
// 返回一个指定名称和形参类型的公共方法的对象
public Method getMethod(String name, Class<?>... parameterTypes)
// 示例代码
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class<User> userServiceClass = User.class;
        Method[] methods = userServiceClass.getMethods();  //获取当前User类所有的公共的方法对象
        for(Method method : methods) {
            System.out.println("method: " + method.toString());
        }
        Method method1 = userServiceClass.getMethod("getStr", String.class);
        System.out.println("method1: " + method1);
        Method method2 = userServiceClass.getMethod("getLongArr", long[].class);
        System.out.println("method2: " + method2);
        Method method3 = userServiceClass.getMethod("getStr", null);
        System.out.println("method3: " + method3);
        Method method4 = userServiceClass.getMethod("test", null);
        System.out.println("method3: " + method4);
        
        User user = new User();
        Object ret1 = method1.invoke(user,"abc");
        Object ret2 = method3.invoke(user, null);
    }

Field类及其用法

// 获取类中所有的属性(public、protected、default、private),但不包括继承的属性,返回 Field 对象的一个数组
Class.getDeclaredFields()
// 获取类中public类型的属性,返回一个包含某些 Field 对象的数组,该数组包含此 Class 对象所表示的类或接口的所有可访问公共字段
Class.getFields()
// 获取类特定的属性,name参数指定了属性的名称
Class.getDeclaredField(String name)
// 获取类中特定的属性,name参数指定了属性的名称
Class.getField(String name)
// 示例代码
	public static void main(String[]args) throws NoSuchFieldException, SecurityException
	{
		Person p = new Person();
		p.setAge(30);
		// 通过Class.getDeclaredField(String name)获取类或接口的指定的已声明字段。
		Field f1 = p.getClass().getDeclaredField("name");
		System.out.println("f1===" + f1);
		// 通过Class.getDeclaredFields()获取类或接口的指定已声明字段。
		Field[] f2 = p.getClass().getDeclaredFields();
		for (Field field : f2) {
			System.out.println("f2--field  " + field);
		}
		// 通过Class.getField(String name)返回一个类或接口的指定的公共成员字段,私有成员报错。
		Field f3_0 = p.getClass().getField("name");
		System.out.println("f3_0===" + f3_0);
		//如果获取age属性(私有成员) 则会报错, age是private
		//Field f3_1 = p.getClass().getField("age");//Exception in thread "main" java.lang.NoSuchFieldException: age
		//System.out.println("f3_1===" + f3_1);
		//通过Class.getFields(),返回 Class 对象所表示的类或接口的所有可访问公共字段。
		Field f4 [] = p.getClass().getFields();
		for (Field field : f4) {
			//因为只有name属性为共有,因此只能遍历出name属性
			System.out.println("f4--field  "+field);
		}
	}

getName、getCanonicalName与getSimpleName的区别

getSimpleName:只获取类名
getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。
getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。

SPI机制

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
参考文章:https://pdai.tech/md/interview/x-interview.html#_1-6-spi%E6%9C%BA%E5%88%B6
SPI整体机制图如下:
在这里插入图片描述
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader

常用的Collection

Set:
TreeSet基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet查找的时间复杂度为 O(1),TreeSet则为O(logN)。
HashSet基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator遍历HashSet得到的结果是不确定的。
LinkedHashSet具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。
List:
ArrayList基于动态数组实现,支持随机访问。
Vector和ArrayList类似,但它是线程安全的。
LinkedList基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈、队列和双向队列。
Queue:
LinkedList可以用它来实现双向队列。
PriorityQueue基于堆结构实现,可以用它来实现优先队列。

常用的Map

TreeMap基于红黑树实现。
HashMap 1.7基于哈希表实现,1.8基于数组+链表+红黑树。
HashTable和HashMap类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入HashTable并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用ConcurrentHashMap来支持线程安全,并且ConcurrentHashMap的效率会更高(1.7 ConcurrentHashMap引入了分段锁, 1.8引入了红黑树)。
LinkedHashMap使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。

什么是WeakHashMap

我们都知道Java中内存是通过GC自动管理的,GC会在程序运行过程中自动判断哪些对象是可以被回收的,并在合适的时机进行内存释放。
GC判断某个对象是否可被回收的依据是,是否有有效的引用指向该对象。如果没有有效引用指向该对象(基本意味着不存在访问该对象的方式),那么该对象就是可回收的。这里的有效引用并不包括弱引用。也就是说,虽然弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收。WeakHashMap内部是通过弱引用来管理entry的,弱引用的特性对应到WeakHashMap上意味着什么呢?WeakHashMap里的entry可能会被GC自动删除,即使程序员没有调用remove()或者clear()方法WeakHashMap的这个特点特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值