Java基础

Java中基本数据类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
数字0-9对应ASCII编码十进制为48-57, 字母a-z对应ASCII编码十进制为97-122,字母A-Z对应ASCII编码十进制为65-90

++和–前置和后置的区别

++,–运算符后置时,先使用变量a原有值参与运算操作,运算操作完成后,变量a的值自增1或者自减1;
++,–运算符前置时,先将变量a的值自增1或者自减1,然后使用更新后的新值参与运算操作。
该操作无法保证原子性

三元运算符

(条件表达式)?表达式1:表达式2;
先判断条件表达式的值,若为true,运算结果为表达式1;若为false,运算结果为表达式2。

do…while

do {
执行语句
………
} while(循环条件);

最简单无限循环格式:

while(true){}

for(;😉{}

break

当break语句出现在嵌套循环中的内层循环时,它只能跳出内层循环,如果想使用break语句跳出外层循环则需要对外层循环添加标记。

下面是如何跳出双层循环

public class BreakDemo {
	public static void main(String[] args) {
		int i, j; // 定义两个循环变量
		itcast: for (i = 1; i <= 9; i++) { // 外层循环
			for (j = 1; j <= i; j++) { // 内层循环
				if (i > 4) { // 判断i的值是否大于4
					break itcast; // 跳出外层循环
				}
				System.out.print("*"); // 打印*
			}
			System.out.print("\n"); // 换行
		}
	}
}

switch

switch (表达式){
	case 目标值1:
		执行语句1
		break;
	case 目标值2:
		执行语句2
		break;
	......
	case 目标值n:
		执行语句n
		break;
	default:
		执行语句n+1
		break;
}
switch (week) {
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
			// 当 week 满足值 1、2、3、4、5 中任意一个时,处理方式相同
			System.out.println("今天是工作日");
			break;
		case 6:
		case 7:
			// 当 week 满足值 6、7 中任意一个时,处理方式相同
			System.out.println("今天是休息日");
			break;
		}
}

选择排序

public static void selectSort(int[] arr) {
	//功能
	//外层循环用来控制数组循环的圈数
	for (int i = 0; i < arr.length-1; i++) {
		//内层循环用来完成元素值比较,把小的元素值互换到要比较的第一个元素中
		for (int j = i+1; j < arr.length; j++) {
			if (arr[i] > arr[j]) {
				int temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
			}
		}
	}
}

冒泡排序

public static void bubbleSort(int[] arr) {
	//功能
	//外层循环用来控制数组循环的圈数
	for (int i = 0; i < arr.length-1; i++) {
		//j < arr.length-1 为了避免角标越界
		//j < arr.length-1-i 为了比较效率,避免重复比较
		//内层循环用来完成元素值比较,把大的元素值互换到后面
		for (int j = 0; j < arr.length-1-i; j++) {
			if (arr[j] > arr[j+1]) {
				int temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
}

二分查找(折半查找)

public static int halfSearch(int[] arr, int number) {
	//定义3个变量,用来记录min, min, mid的位置
	int min = 0;
	int max = arr.length-1;
	int mid = 0;
		while (min <= max) {
           mid = (min+max)/2;
		//没找了, 更新范围,继续比较
		//更新范围
		if (arr[mid] > number) {
			//在左边
			max = mid-1;
		} else if(arr[i] < number){
			//在右边
			min = mid+1;
		}
		else{
              return mid ;
          }
	 
	return -1;
}

面向对象,面向过程

java是面向对象的语言,C语言是面向过程

面向对象共有三个特征:封装,继承,多态。

类和对象的区别

类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体
类中可以定义事物的属性和行为。而对象是通过描述的这个类,使用new关键字创建出来,通过对象就可以调用该对象具体的属性和功能了。

局部变量和成员变量的区别

区别一:定义的位置不同
定义在类中的变量是成员变量
定义在方法中或者{}语句里面的变量是局部变量
区别二:在内存中的位置不同
成员变量存储在堆内存的对象中
局部变量存储在栈内存的方法中
区别三:声明周期不同
成员变量随着对象的出现而出现在堆中,随着对象的消失而从堆中消失
局部变量随着方法的运行而出现在栈中,随着方法的弹栈而消失
区别四:初始化不同
成员变量因为在堆内存中,所有默认的初始化值
局部变量没有默认的初始化值,必须手动的给其赋值才可以使用。

this关键字

this关键字,本类对象的引用
this是在方法中使用的,哪个对象调用了该方法,那么,this就代表调用该方法的对象引用
this什么时候存在的?当创建对象的时候,this存在的
this的作用:用来区别同名的成员变量与局部变量(this.成员变量)
public void setName(String name) {
this.name = name;
}

继承

子类会自动拥有父类所有可继承的属性和方法
类只支持单继承,不允许多继承
多个类可以继承一个父类
多层继承是可以的

当在程序中通过对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。

super

当子父类中出现了同名成员变量时,在子类中若要访问父类中的成员变量,必须使用关键字super来完成。super用来表示当前对象中包含的父类对象空间的引用。

面试题:重载和重写区别

重写

子类中出现与父类一模一样的方法时,会出现覆盖操作,也称为override重写、复写或者覆盖。

重载

Java允许在一个类中定义多个名称相同的方法,但是参数的类型或个数必须不同,这就是方法的重载。
下面的三个方法互为重载关系
public static int add(int x,int y) {逻辑} //两个整数加法
public static int add(int x,int y,int z) {逻辑} //三个整数加法
public static int add(double x,double y) {逻辑} //两个小数加法

重载方法参数必须不同:
参数个数不同,如method(int x)与method(int x,int y)不同
参数类型不同,如method(int x)与method(double x)不同g
参数顺序不同,如method(int x,double y)与method(double x,int y)不同
重载只与方法名与参数类型相关与返回值无关
如void method(int x)与int method(int y)不是方法重载,不能同时存在
重载与具体的变量标识符无关
如method(int x)与method(int y)不是方法重载,不能同时存在

面试:抽象类和接口的区别

实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

抽象类

abstract关键字修饰的类是抽象类

特点
1、抽象类和抽象方法都需要被abstract修饰。抽象方法一定要定义在抽象类中。
2、抽象类不可以直接创建对象,原因:调用抽象方法没有意义。
3、只有覆盖了抽象类中所有的抽象方法后,其子类才可以创建对象。否则该子类还是一个抽象类。
之所以继承抽象类,更多的是在思想,是面对共性类型操作会更简单。

1、抽象类一定是个父类?
是的,因为不断抽取而来的。
2、抽象类中是否可以不定义抽象方法。
是可以的,那这个抽象类的存在到底有什么意义呢?不让该类创建对象,方法可以直接让子类去使用
3、抽象关键字abstract不可以和哪些关键字共存?
1、private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法。互相矛盾。
2、final,
3、static,

接口

比抽象类更为抽象的”类”
接口定义时需要使用interface关键字
接口中的方法均为公共访问的抽象方法
接口中无法定义普通的成员变量

类与接口的关系为实现关系,即类实现接口。实现的动作类似继承,只是关键字不同,实现使用implements。

特点:
1、接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量,其值不能改变。后面我们会讲解static与final关键字
2、接口中可以定义方法,方法也有固定的修饰符,public abstract
3、接口不可以创建对象。
4、子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
接口的出现避免了单继承的局限性
多个接口之间可以使用extends进行继承

多态

最终多态体现为父类引用变量可以指向子类对象。
父类类型 变量名 = new 子类类型();
多态的前提是必须有子父类关系或者类实现接口关系,否则无法完成多态。
在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。

多态成员变量
当子父类中出现同名的成员变量时,多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记:编译和运行都参考等号的左边。编译运行看左边。

多态成员方法
编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败。
运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
简而言之:编译看左边,运行看右边。

interfaceof

通过instanceof关键字来判断某个对象是否属于某种数据类型

向上转型:本身就是向上转型
向下转型:强转

格式: 对象名 instanceof 类名
返回值: true, false
作用: 判断指定的对象 是否为 给定类创建的对象

构造方法

对象创建时要执行的方法
构造方法是专门用来创建对象的,也就是在new对象时要调用构造方法

编译Java文件时,编译器会自动给class文件中添加默认的构造方法。如果在描述类时,我们显示指定了构造方法,那么,当在编译Java源文件时,编译器就不会再给class文件中添加默认构造方法。

若创建对象时不需要明确具体的数据,这时可以不用书写构造方法(不书写也有默认的构造方法)。

构造方法的细节:
一个类中可以有多个构造方法,多个构造方法是以重载的形式存在的
构造方法是可以被private修饰的,作用:其他程序无法创建该类的对象。

构造方法的体现:
构造方法没有返回值类型。也不需要写返回值。因为它是为构建对象的,对象创建完,方法就执行结束。
构造方法名称必须和类型保持一致。
构造方法没有具体的返回值。

构造方法和一般方法区别?
构造方法在对象创建时就执行了,而且只执行一次。
一般方法是在对象创建后,需要使用时才被对象调用,并可以被多次调用。

构造方法之间的调用,可以通过this关键字来完成。
调用其他构造方法的语句必须定义在构造方法的第一行,原因是初始化动作要最先执行
构造方法调用格式:
this(参数列表);

class Person {
	// Person的成员属性
	private int age;
	private String name;

	// 无参数的构造方法
	Person() {
	}

	// 给姓名初始化的构造方法
	Person(String nm) {
		name = nm;
	}

	// 给姓名和年龄初始化的构造方法
	Person(String nm, int a) {
		// 由于已经存在给姓名进行初始化的构造方法 name = nm;因此只需要调用即可
		// 调用其他构造方法,需要通过this关键字来调用
		this(nm);
		// 给年龄初始化
		age = a;
	}
}

在创建子类对象时,父类的构造方法会先执行,因为子类中所有构造方法的第一行有默认的隐式super();

Java体系在设计,定义了一个所有对象的父类Object

final

有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写
final修饰的变量称为常量,这些变量只能赋值一次
引用类型的变量值为对象地址值,地址值不能更改,但是地址内的对象属性值可以修改

面试题:final在java中

final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

static

1.被static修饰的成员变量属于类,不属于这个类的某个对象
多个对象在访问或修改static修饰的成员变量时,其中一个对象将static成员变量值进行了修改,其他对象中的static成员变量值跟着改变,即多个对象共享同一个static成员变量
2.被static修饰的成员可以并且建议通过类名直接访问。
访问静态成员的格式:
类名.静态成员变量名
类名.静态成员方法名(参数)

静态内容是优先于对象存在,只能访问静态,不能使用this/super。静态修饰的内容存于静态区。

class Demo {
	//成员变量
	public int num = 100;
	//静态方法
	public static void method(){
		//this.num; 不能使用this/super。
		System.out.println(this.num);
	}
}

同一个类中,静态成员只能访问静态成员

class Demo {
	//成员变量
	public int num = 100;
	//静态成员变量
	public static int count = 200;
	//静态方法
	public static void method(){
		//System.out.println(num); 静态方法中,只能访问静态成员变量或静态成员方法
		System.out.println(count);
	}
}

main方法为静态方法仅仅为程序执行入口,它不属于任何一个对象,可以定义在任意类中。

接口中的每个成员变量都默认使用public static final修饰。
所有接口中的成员变量已是静态常量,由于接口没有构造方法,所以必须显示赋值。可以直接用接口名访问。

内部类

将类写在其他类的内部,可以写在其他类的成员位置和局部位置

内部类分为成员内部类与局部内部类。

匿名内部类(实际应用)

public abstract class Person{
	public abstract void eat();
}
//定义并创建该父类的子类对象,并用多态的方式赋值给父类引用变量
Person  p = new Person(){
	public void eat() {
		System.out.println(“我吃了”);
}
};
//调用eat方法
p.eat();
new Person(){
	public void eat() {
		System.out.println(“我吃了”);
}
}.eat();

访问修饰符权限

在这里插入图片描述
归纳一下:在日常开发过程中,编写的类、方法、成员变量的访问
要想仅能在本类中访问使用private修饰;
要想本包中的类都可以访问不加修饰符即可;
要想本包中的类与其他包中的子类可以访问使用protected修饰
要想所有包中的所有类都可以访问使用public修饰。
注意:如果类用public修饰,则类名必须与文件名相同。一个文件中只能有一个public修饰的类。

构造代码块

是定义在类中成员位置的代码块
特点:
优先于构造方法执行,构造代码块用于执行所有对象均需要的初始化动作
每创建一个对象均会执行一次构造代码块。

 //构造代码块
	{
		System.out.println("构造代码块执行了");
	}

静态代码块

是定义在成员位置,使用static修饰的代码块。

//静态代码块
	static{
		System.out.println("静态代码块执行了");
	}

abstract与private,static, final不能同时使用

Object类

equals(),toString(),wait(),notify(),notifyAll()

面试题:equals和==的区别?

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

public boolean equals(Object obj){
        return (this == obj);
}

下面是String比较

String s3 = "abc";
String s4 = new String("abc");
System.out.println(s3==s4);//false
System.out.println(s3.equals(s4));//true,

因为String 重写了 Object 的 equals 方法,把引用比较改成了值比较
源码如下:

public boolean equals(Object anObject){
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

s3和s4的创建方式有什么不同呢?
s3创建,在内存中只有一个对象。这个对象在字符串常量池中
s4创建,在内存中有两个对象。一个new的对象在堆中,一个字符串本身对象,在字符串常量池中

面试: String,StringBuffer和StringBuilder区别?

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

面试:String类是否可以被继承?

String是对象
不能,String类有final修饰符,而final修饰的类是不能被继承的
在这里插入图片描述

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

String str="i"与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

自动装箱拆箱

自动拆箱:对象转成基本数值
自动装箱:基本数值转成对象

集合

在这里插入图片描述
Collection
List
ArrayList
LinkedList
Vector
Stack
Set
HashSet
LinkedHashSet
TreeSet
Map
HashMap
LinkedHashMap
TreeMap
ConcurrentHashMap
Hashtable
在这里插入图片描述

Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
集合中把这种取元素的方式描述在Iterator接口中。
hasNext()方法:用来判断集合中是否有下一个元素可以迭代。如果返回true,说明可以迭代。
next()方法:用来返回迭代的下一个元素,并把指针向后移动一位。

面试:HashMap 和 Hashtable 有什么区别?

存储:HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。
线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

如何实现数组和 List 之间的转换?

数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。

ArrayList 和 Vector 的区别是什么?

线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
性能:ArrayList 在性能方面要优于 Vector。
扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

并发修改异常

发生了错误 java.util.ConcurrentModificationException这是什么原因呢?
在迭代过程中,使用了集合的方法对元素进行操作。导致迭代器并不知道集合中的变化,容易引发数据的不确定性。

泛型

明确集合中元素的类型。这种方式称为:泛型。
泛型的通配符<?>

ArrayList

集合中存储的元素必须是引用类型数据

ArrayList内部封装了一个长度可变的数组,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。

ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快
LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。
Vector集合数据存储的结构是数组结构,为JDK中最早提供的集合。Vector中提供了一个独特的取出方式,就是枚举Enumeration
HashSet集合,采用数组+链表(哈希表)结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。

给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中

如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

HashSet:
元素唯一,不能重复
底层结构是 哈希表结构
元素的存与取的顺序不能保证一致
如何保证元素的唯一的?
重写hashCode() 与 equals()方法
LinkedHashSet:
元素唯一不能重复
底层结构是 哈希表结构 + 链表结构
元素的存与取的顺序一致

Collection中的集合称为单列集合,Map中的集合称为双列集合
Map中的集合不能包含重复的键,值可以重复

在Map类设计时,提供了一个嵌套接口:Entry。Entry将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。
Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。

异常

Exception有继承关系,它的父类是Throwable。Throwable是Java 语言中所有错误或异常的超类,即祖宗类。
RuntimeException及其它的子类只能在Java程序运行过程中出现
与异常Exception平级的有一个Error,它是Throwable的子类,它用来表示java程序中可能会产生的严重错误。

异常与错误区别

异常:指程序在编译、运行期间发生了某种异常(XxxException),我们可以对异常进行具体的处理。若不处理异常,程序将会结束运行。
错误:指程序在运行期间发生了某种错误(XxxError),Error错误通常没有具体的处理方式,程序将会结束运行。Error错误的发生往往都是系统级别的问题,都是jvm所在系统发生的,并反馈给jvm的。我们无法针对处理,只能修正代码。

throw

throw关键字,它用来抛出一个指定的异常对象
使用格式:
throw new 异常类名(参数);

声明异常throws

声明异常格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2… { }

捕获异常try…catch…finally

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。

自定义异常

定义方法:编译时异常继承Exception,运行时异常继承RuntimeException

常见异常

NullPointerException 空指针异常
ClassNotFoundException 指定类不存在
NumberFormatException 字符串转换为数字异常
IndexOutOfBoundsException 数组下标越界异常
ClassCastException 数据类型转换异常
FileNotFoundException 文件未找到异常
NoSuchMethodException 方法不存在异常
IOException IO 异常
SocketException Socket 异常

递归

指在当前方法内调用自己的这种现象
递归打印所有子目录中的文件路径

public class FileDemo2 {
	public static void main(String[] args) {
		File file = new File("d:\\test");
		getFileAll(file);
	}
	//获取指定目录以及子目录中的所有的文件
	public static void getFileAll(File file) {
		File[] files = file.listFiles();
		//遍历当前目录下的所有文件和文件夹
		for (File f : files) {
			//判断当前遍历到的是否为目录
			if(f.isDirectory()){
				//是目录,继续获取这个目录下的所有文件和文件夹
				getFileAll(f);
			}else{
				//不是目录,说明当前f就是文件,那么就打印出来
				System.out.println(f);
			}
		}
	}
}

面试:BIO、NIO、AIO 有什么区别?

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

IO

字节流
字节输入流 InputStream
FileInputStream 操作文件的字节输入流
BufferedInputStream高效的字节输入流
ObjectInputStream 反序列化流
字节输出流 OutputStram
FileOutputStream 操作文件的字节输出流
BufferedOutputStream 高效的字节输出流
ObjectOuputStream 序列化流
PrintStream 字节打印流
字符流
字符输入流 Reader
FileReader 操作文件的字符输入流
BufferedReader 高效的字符输入流
InputStreamReader 输入操作的转换流(把字节流封装成字符流)
字符输出流 Writer
FileWriter 操作文件的字符输出流
BufferedWriter 高效的字符输出流
OutputStreamWriter 输出操作的转换流(把字节流封装成字符流)
PrintWriter 字符打印流

OutputStreamWriter和InputStreamReader是字符和字节的桥梁:也可以称之为字符转换流

用于从流中读取对象的
操作流 ObjectInputStream 称为 反序列化流
用于向流中写入对象的操作流 ObjectOutputStream 称为 序列化流

当一个对象要能被序列化,这个对象所属的类必须实现Serializable接口
serialVersionUID. 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
当一个类的对象需要被序列化时,某些属性不需要被序列化,这时不需要序列化的属性可以使用关键字transient修饰
同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加载时的数据,不会被序列化

线程有哪些状态?

NEW 尚未启动
RUNNABLE 正在执行中
BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
WAITING 永久等待状态
TIMED_WAITING 等待指定的时间重新被唤醒的状态
TERMINATED 执行完成

NEW 状态是指线程刚创建, 尚未启动

RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等

BLOCKED 这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区
WAITING 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

TIMED_WAITING 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

sleep() 和 wait() 有什么区别?

类的不同:sleep() 来自 Thread,wait() 来自 Object。
释放锁:sleep() 不释放锁;wait() 释放锁。
用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

线程池创建有七种方式,最核心的是最后一种:

newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;

newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;

newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;

newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;

newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;

newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;

ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。阿里只要求这种

线程

一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法。
另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法,将线程任务代码定义到run方法中。

方式1:创建线程对象时,直接重写Thread类中的run方法

new Thread() {
			public void run() {
				for (int x = 0; x < 40; x++) {
					System.out.println(Thread.currentThread().getName()
							+ "...X...." + x);
				}
			}
		}.start();

方式2:使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法

Runnable r = new Runnable() {
			public void run() {
				for (int x = 0; x < 40; x++) {
					System.out.println(Thread.currentThread().getName()
							+ "...Y...." + x);
				}
			}
		};
		new Thread(r).start();

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源

Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
ExecutorService:线程池类
Future submit(Callable task):获取线程池中的某一个线程对象,并执行线程中的call()方法
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

死锁

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。

使用Lock接口,以及其中的lock()方法和unlock()方法替代同步

等待唤醒机制所涉及到的方法:
wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

ThreadLocal 的经典使用场景是数据库连接和 session 管理等。

说一下 synchronized 底层实现原理?

synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

synchronized 和 volatile 的区别是什么?

volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

synchronized 和 Lock 有什么区别?

synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

synchronized 和 ReentrantLock 区别是什么?

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

主要区别如下:

ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。

说一下 atomic 的原理?

atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

动态代理

运行时动态生成代理类
JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。

如何实现对象克隆?

实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

深拷贝和浅拷贝区别是什么?

浅克隆:当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将复制。

final、finally、finalize 有什么区别?

final:是修饰符,如果修饰类,此类不能被继承;如果修饰方法和变量,则表示此方法和此变量不能在被改变,只能使用。
finally:是 try{} catch{} finally{} 最后一部分,表示不论发生任何情况都会执行,finally 部分可以省略,但如果 finally 部分存在,则一定会执行 finally 里面的代码。
finalize: 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法。

forward 和 redirect 的区别?

forward 是转发 和 redirect 是重定向:

地址栏 url 显示:foward url 不会发生改变,redirect url 会发生改变;
数据共享:forward 可以共享 request 里的数据,redirect 不能共享;
效率:forward 比 redirect 效率高。

MySQL

MySQL数据库的SQL语句不区分大小写,建议使用大写
like

五个聚合函数:
count:统计指定列不为NULL的记录行数;
sum:计算指定列的数值和,如果指定列;
max:计算指定列的最大值,如果指定列是字符串类型,那么使用字符串类型不是数值类型,那么计算结果为0排0序运算;
min:计算指定列的最小值,如果指定列是字符串类型,那么使用字符串排序运算;
avg:计算指定列的平均值,如果指定列类型不是数值类型,那么计算结果为0;

分组操作中的having子语句,是用于在分组后对数据进行过滤的,作用类似于where条件。

having与where的区别:
having是在分组后对数据进行过滤.
where是在分组前对数据进行过滤
having后面可以使用分组函数(统计函数)
where后面不可以使用分组函数。

Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动

  1. 注册驱动
    1. 获取连接
    2. 获取预处理对象
    3. SQL语句占位符设置实际参数
    4. 执行SQL语句
    5. 处理结果集(遍历结果集合)
    6. 释放资源

开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们采用连接池技术,来共享连接Connection。这样我们就不需要每次都创建连接、释放连接了,这些操作都交给了连接池。用池来管理Connection,这样可以重复使用Connection

类的加载

此处面试涉及到jvm知识,其他文章中已经写明

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象
连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化
就是我们以前讲过的初始化步骤

类加载器

负责将.class文件加载到内在中,并为之生成对应的Class对象。

Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载
比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录
System ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。

加载class文件到内存后如何使用,这就要研究反射

反射机制

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

获取Class对象的三种方式

方式一: 通过Object类中的getObject()方法。getClass
Person p = new Person();
Class c = p.getClass();

方式二: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
Class c2 = Person.class;

方式三: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
Class c3 = Class.forName(“Person”);
**注意:**第三种和前两种的区别
前两种你必须明确Person类型.
后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了

通过反射获取构造方法,私有构造方法

泛型擦除

程序编译后产生的.class文件中是没有泛型约束的,这种现象我们称为泛型的擦除。那么,我们可以通过反射技术,来完成向有泛型约束的集合中,添加任意类型的元素
其实实际中根本不用,反正我工作中没用到。

集合,ArrayList,HashMap

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值