最近面试复习+总结知识点
javac.exe:编译.java文件
java.exe:执行编译好的.class文件
javadoc.exe:生成java说明文档
jdb.exe:java调试器
avaprof.exe:剖析工具
对象的初始化过程:
关于对象的初始化过程,以下顺序正确的是:
对象的初始化过程遵循的顺序是:
①实例化对象时,成员变量初始化为默认值。
②将成员变量赋为定义类时设置的初值。
③通过初始化块给成员变量赋值 。
④调用构造方法时,使用构造方法所带的参数初始化成员变量
例:
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
输出:YXYZ
解析:
初始化过程:
1.初始化父类中的静态成员变量和静态代码块 ;
2.初始化子类中的静态成员变量和静态代码块 ;
3.初始化父类的普通成员变量和代码块,再执行父类的构造方法;
4.初始化子类的普通成员变量和代码块,再执行子类的构造方法;
(1)初始化父类的普通成员变量和代码块,执行 Y y=new Y(); 输出Y
(2)再执行父类的构造方法;输出X
(3) 初始化子类的普通成员变量和代码块,执行 Y y=new Y(); 输出Y
(4)再执行子类的构造方法;输出Z
以下关于集合类ArrayList、LinkedList、HashMap描述错误的是(C)
HashMap实现Map接口,它允许任何类型的键和值对象,并允许将null用作键或值
ArrayList和LinkedList均实现了List接口
添加和删除元素时,ArrayList的表现更佳
ArrayList的访问速度比LinkedList快
解答:
-
List 是一个有序集合,可以存放重复的数据 (有序:存进是什么顺序,取出时还是什么顺序)
(1).ArrayList 底层是数组适合查询,不适合增删元素。
(2).LiskedList 底层是双向链表适合增删元素,不适合查询操作。
(3).Vector 底层和ArrayList相同,但是Vector是线程安全的,效率较低很少使用-
Set 是一个无序集合,不允许放重复的数据 (无序不可重复,存进和取出的顺序不一样)
(1).HashSet 底层是哈希表/散列表
(2).TreeSet 继承sartedSet接口(无需不可重复,但存进去的元素可以按照元素的大小自动排序) -
Map 是一个无序集合,以键值对的方式存放数据,键对象不允许重复,值对象可以重复。
(1).HashMap实现不同步,线程不安全。 HashTable线程安全 (2).HashMap中的key-value都是存储在Entry中的。 (3).HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性
-
线程创建的5种方法:
1 继承Thread类
public class CreateThread01 {
static class Mythread extends Thread{
@Override
public void run() {
System.out.println("线程创建的第一种方法");
}
}
public static void main(String[] args) {
new Mythread().start();
}
}
2 实现Runnable接口
static class MyRun implements Runnable{
@Override
public void run() {
System.out.println("线程创建的第二种方法");
}
}
public static void main(String[] args) {
new Mythread().start();
new Thread(new MyRun()).start();
}
3 使用lambda表达式
new Thread(()->{
System.out.println("线程创建的第三种方法");
}).start();
4 实现Callable接口
static class MyCall implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("线程创建的第四种方法");
return "线程创建的第四种方法";
}
}
public static void main(String[] args) {
new Mythread().start();
new Thread(new MyRun()).start();
new Thread(()->{
System.out.println("线程创建的第三种方法");
}).start();
Thread thread = new Thread(new FutureTask<String>(new MyCall()));
thread.start();
}
5使用线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(()->{
System.out.println("使用线程池创建线程");
});
executorService.shutdown();
}
用线程池执行线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> submit = executorService.submit(new MyCall());
String s = submit.get();
System.out.println(s);
执行 execute()方法和 submit()方法的区别是什么呢?
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
实现 Runnable 接口和 Callable 接口的区别
Runnable自 Java 1.0 以来一直存在,但Callable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Runnable 接口 不会返回结果或抛出检查异常,但是 Callable 接口 可以。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口 ,这样代码看起来会更加简洁。
非静态方法里不能存在静态的局部变量。!!
如:
public class Test{
public int aMethod(){
static int i=0;//非静态方法里不能存在静态的局部变量
i++;
return i;
}
public static void main (String args[]){
Test test=new Test();
test.aMethod();
int j=test.aMethod();
System.out.println(j);
}
}
此结果输出为:编译失败
例:
class Base{
public Base(String s) {
System.out.print("B");
}
}
public class Derived extends Base{
public Derived(String s) {
System.out.println("D");
}
}
public static void main(String []args){
new Derived("C");
}
此结果为编译错误
解析:
实例化子类时会先调用父类的构造器,子类构造器不显示提供调用父类构造器的方法(super())时,则子类默认调用父类无参的构造器,但是此题中父类提供了有参构造器,编译器不会再默认提供无参构造器,所以编译器会因为找不到构造器而报错。
以下对抽象类的描述正确的是(C)
A:抽象类没有构造方法
B:抽象类必须提供抽象方法
C:有抽象方法的类一定是抽象类
D:抽象类可以通过new关键字直接实例化
解析:抽象类可以有构造方法;抽象类中可以没有抽象方法;有抽象方法的类一定是抽象类; 抽象类不能被直接实例化。
在类设计中,类的成员变量要求仅仅能够被同一package下的类访问,请问应该使用下列哪个修饰词(D)
A:protected
B:public
C:private
D:不需要任何修饰词
解析:
只要记住protected一个特点是只要子类都能访问,不管在不在一个包。
例:
public class A {
public static void main(String[] args){
for(int i=0;i<2;i++){
switch(i){
case 0: System.out.print(i);
case 1: System.out.print(i);
break;
}
}
System.out.println("End");
}
}
输出为 : 001End
解析: 因为case 0 后没有break 会继续执行case 1 所以i=0 的时候会匹配到 case0 和case 1两个 输出00
case 1的时候 输出1 之后输出END
以下代码的输出结果是
public class B
{
public static B t1 = new B();
public static B t2 = new B();
{
System.out.println("构造块");
}
static
{
System.out.println("静态块");
}
public static void main(String[] args)
{
B t = new B();
}
}
输出 :构造块 构造块 静态块 构造块
解析:
开始时JVM加载B.class,对所有的静态成员进行声明,t1 t2被初始化为默认值,为null,又因为t1
t2需要被显式初始化,所以对t1进行显式初始化,初始化代码块→构造函数(没有就是调用默认的构造函数),咦!静态代码块咋不初始化?因为在开始时已经
对static部分进行了初始化,虽然只对static变量进行了初始化,但在初始化t1时也不会再执行static块了,因为JVM认为这是第二次加载
类B了,所以static会在t1初始化时被忽略掉,所以直接初始化非static部分,也就是构造块部分(输出’‘构造块’’)接着构造函数(无输
出)。接着对t2进行初始化过程同t1相同(输出’构造块’),此时就对所有的static变量都完成了初始化,接着就执行static块部分(输出’静
态块’),接着执行,main方法,同样也,new了对象,调用构造函数输出(‘构造块’)
参数传递方式:
下列代码执行结果是()
public class Test {
public static void main(String[] args) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
operate (a,b);
System.out.println(a+","+b);
}
static void operate(StringBuffer x, StringBuffer y) {
x.append(y);
y = x;
}
}
结果:显示 “AB,B”
解析:此题考点为参数的值传递方式,参数传递时,方法内得到的是实参的一份拷贝.
对于引用类型变量来说,拷贝的意思是新变量和原变量指向统一对象,所以对方法参数调用更新方法对对象做出改变可以影响实参,但是对局部变量重新赋值不会影响实参.
在operate方法中形参变量x和实参变量a指向同一块内存地址,所以对x的操作x.append(y)影响变量a所指向对象的内容(变为AB);y=x是将x的内存地址赋值给了局部变量y,对b没有影响,所以b的内容还是B;
int i=6 ,j=8, k=10,m=7:
if(i<j|m>++k){
k++;
}
&& 和 || 和 & 和 | :
|| 和 && 定义为逻辑运算符,而 | 和 & 定义为位运算符
&& 如果两个操作数都非零,则条件为真;
|| 如果两个操作数中有任意一个非零,则条件为真。
& 按位与操作,按二进制位进行"与"运算。运算规则:(有 0 则为 0)
| 按位或运算符,按二进制位进行"或"运算。运算规则:(有 1 则为 1)
&&:
短路与(并且),两边都为真则为真,见false则false
如果第1个数为false,则发生短路(第2个数不走了)
||:
短路或(或者),有一边为真则为真,见true则true
如果第1个数为true,则发生短路(第2个数不走了)
&按位与
& 既是位运算符又是逻辑运算符,&的两侧可以是int,也可以是boolean表达式,当&两侧是int时,要先把运算符两侧的数转化为二进制数再进行运算,而短路与(&&)的两侧要求必须是布尔表达式。
注意 :&为真的条件是两侧表达式都为真,但是即使我们判断出左侧表达式的值为false,程序也还是要继续执行去判断右侧的表达式值的真假。
int i=2,j=4;
System.out.println((++i==2)&(j++==4));//false
System.out.println("i="+i+" ,j="+j);//3,5
System.out.println(12&5);//与运算12=(1100)2 5=(0101) 1100+0101=0100 4
System.out.println((2==2)&(4==4));//真真 true
System.out.println((2==2)&(4!=4));//真假 false
System.out.println((2!=2)&(4==4));//假真 false
System.out.println((2!=2)&(4!=4));//假假 false
|按位或
(|)和(||)成立的条件是只要一侧表达式为真,结果就为真,在(|)运算时,无论左侧的表达式的值为真还是为假,都要判断右侧的表达式的值的真假,而(||)在运算时,只要左侧表达式的值为真,就不再判断右侧表达式的值了
例:
class A{
private String name;
}
class B extends A{
void f(){
System.out.println(super.name);
}
}
关于上述代码,叙述正确的是:编译失败
解析: 私有属性name不能在类的外部被访问(private String name)
给定如下Java代码,编译运行后,输出的结果将是
public class Test {
public static void main(String args[]) {
String s1 = new String("Test");
String s2 = new String("Test");
if (s1 == s2)
System.out.println("Same");
if (s1.equals(s2))
System.out.println("Equals");
}
}
输出:Equals
解析:
1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址
2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
1、基本数据类型
基本数据类型只有8种,可按照如下分类
①整数类型:long、int、short、byte
②浮点类型:float、double
③字符类型:char
④布尔类型:boolean
基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
---|---|---|---|---|
byte |
8 | 1 | 0 | -128 ~ 127 |
short |
16 | 2 | 0 | -32768 ~ 32767 |
int |
32 | 4 | 0 | -2147483648 ~ 2147483647 |
long |
64 | 8 | 0L | -9223372036854775808 ~ 9223372036854775807 |
char |
16 | 2 | 'u0000' | 0 ~ 65535 |
float |
32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
double |
64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
boolean |
1 | false | true、false |
2、引用数据类型
引用数据类型非常多,大致包括:
类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型
例如,String
类型就是引用类型。
简单来说,所有的非基本数据类型都是引用数据类型
基本数据类型和引用数据类型的区别
1、存储位置
基本变量类型
- 在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
引用变量类型
- 只要是引用数据类型变量,其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址
ps:通过变量地址可以找到变量的具体内容,就如同通过房间号可以找到房间一般
2、传递方式
基本变量类型
- 在方法中定义的非全局基本数据类型变量,调用方法时作为参数是按数值传递的
引用变量类型
- 引用数据类型变量,调用方法时作为参数是按引用传递的,传递的是引用的副本
下面代码的执行结果是()
public class Main {
public static void main(String[] args) {
int[][] arr = new int[10][];
System.out.println(arr[0][0]);
}
}
结果:抛出NullPointerException
解析:数组在使用之前一定要先定义好长度或者用new int [10] []{{1,2,3,4,5,6,7,8,9,10},,……}的形式直接赋值,new int[10][];
只指定了第一维的长度。
二维数组应该这样初始化:
int a[ ] [4]={1,2,3,4,5,6,7,8,9,10,11,12} ;
int a[ 3 ] [4]={1,2,3,4,5,6,7,8,9,10,11,12} ;
⑴ 分行进行初始化
int a[2] [3]={{1,2,3},{4,5,6}};
在{ }内部再用{ }把各行分开,第一对{ }中的初值1,2,3是0行的3个元素的初值。第二对{ }中的初值4,5,6是1行的3个元素的初值。相当于执行如下语句:
int a[2] [3];
a[0] [0]=1;a[0] [1]=2;a[0] [2]=3;a[1] [0]=4;a[1] [1]=5;a[1] [2]=6;
注意,初始化的数据个数不能超过数组元素的个数,否则出错。
⑵ 不分行的初始化
int a[2] [3]={ 1,2,3,4,5,6};
把{ }中的数据依次赋给a数组各元素(按行赋值)。即a[0] [0]=1; a[0] [1]=2;a[0] [2]=3;a[1] [0]=4;a[1] [1]=5;a[1] [2]=6;
⑶ 为部分数组元素初始化
static int a[2] [3]={{1,2},{4}};
第一行只有2个初值,按顺序分别赋给a[0] [0]和a[0] [1];第二行的初值4赋给a[1] [0]。由于存储类型是static,故其它数组元素的初值为0。注:某些C语言系统(如:Turbo C)中,存储类型不是static的变量或数组的初值也是0。
static int a[2] [3]={ 1,2};
只有2个初值,即a[0] [0]=1,a[0] [1]=2,其余数组元素的初值均为0。
⑷ 可以省略第一维的定义,但不能省略第二维的定义。系统根据初始化的数据个数和第2维的长度可以确定第一维的长度。
int a[ ][3]={ 1,2,3,4,5,6};
a数组的第一维的定义被省略,初始化数据共6个,第二维的长度为3,即每行3个数,所以a数组的第一维是2。
一般,省略第一维的定义时,第一维的大小按如下规则确定:
初值个数能被第二维整除,所得的商就是第一维的大小;若不能整除,则第一维的大小为商再加1。例如,int a[ ][3]={ 1,2,3,4};等价于:int a[2] [3]={ 1,2,3,4};
若分行初始化,也可以省略第一维的定义。下列的数组定义中有两对{ },已经表示a数组有两行。
static int a[ ][3]={{1,2},{4}};
二维数组初始化
https://blog.51cto.com/913061291/1306286
创建String 对象:
(1) String s1 = "mpptest"
(2) String s2 = new String();
(3) String s3 = new String("mpptest");
String数组对象
String s[] = new String[8];
int[] i=new int[6];
String[] s= new String[]{"m","i","k","e"}; 或者 String[] s= {"m","i","k","e"};
创建Char对象
字符型数据类型。用于存放单个字符。用单引号【' '】括住。
单引号中只能为一个字符 ,当出现两个及以上的字符时或没有字符时,编译出错.
而双引号在字符串常量时使用,可以表示0到多个字符组成的字符串。如:char s1[]="a"; char s2[] ="a1A"; char s3[] ="";//可以没有任何字符。
2字节,16位。char在java中是16位,因为java用的是Unicode。一个16位的编码所能产生的字符只有65536个。
Unicode码包括ASCII码。大多数计算机采用ASCII(美国标准信息交换码),它是表示所有大小写字母、数字、标点符号和控制字符的8位编码表。从'\u0000'到'\u007F'对应128个ASCII字符。
因为char是16位的,采取的Unicode的编码方式,所以char就有以下的初始化方式:
char c='c'; //字符,可以是汉字,因为是Unicode编码。需要加单引号。
char c=十进制数,八进制数,十六进制数都可以; //可以用整数赋值【整数范围:0~65535】。输出字符编码表中对应的字符。
char c='\u数字'; //用字符的编码值来初始化,如:char='\0',表示结束符,它的ascll码是0,这句话的意思和 char c=0 是一个意思。
char x1 = 65;//为char类型变量x1 赋值常量值 65。十进制编码的ASCII码表中字符'A'对应的是65
char x11 = 41;//')'的ASCII码为十进制数是41
char x12 = 101;//'e'的ASCII码为十进制数是101
char x2 = 'A';//ASCII字符 'A' 的Unicode码是0041。zi
char x3 = '\u0041'
Char运算:
(1)char+char,char+int——类型均提升为int,附值char变量后,输出字符编码表中对应的字符。
char m=‘a’+1; -->b //提升为int,计算结果98对应的字符是b。
(2)自增和自减操作符可以用在char类型变量上,这会得到字符之前或者之后的Unicode字符。
char x1 = 'A';
System.err.println(++x1); //输出B
char a = 65; -->A //为char类型变量 a 赋值常量值 65。
char b = ‘a’+3; -->D // 65+3=68,ASCII对应的字符为 D【注意:这里查询的是十进制编码的ASCII字符集】
char c = a+3; -->报错 //无法从int类型转换为char类型,接下来让我们了解下为什么会不能这样运算:
首先,我们先知道在jvm内存机制中,char类型数据运算是将字符在ASCII表对应的整数以int类型参与运算(可以认为’ A'=65)
常量(65)与常量(3)运算得到一个新的常量(68),常量赋值给变量(b),不存在强制转换,只要这个接受变量(b)的类型范围大于这个常量即可。
而变量声明时需要定义数据类型(例:char a),内存就为这个变量划分一个char类型大小的空间,其中变量(a)的值是可变的,而常量(3)的值是不变的,两个运算得到的还是一个变量,本例中(a+3)是int类型的变量,而int类型变量(a+3)赋值给char类型变量(c)需要强制转换,因此会报错。
若有定义语句: int a=10 ; double b=3.14 ; 则表达式 'A'+a+b 值的类型是()
double
解析:因为按照类型大的与类型小的运算,强制转换类型小的进行运算的规则,double>int>char,因此结果是double类型。
1.在表达式中,char 和 short 类型的值,无论有符号还是无符号,都会自动转换成 int 或者 unsigned int(如果 short 的大小和 int 一样,unsigned short 的表示范围就大于 int,在这种情况下,unsigned short 被转换成 unsigned int)。因为它们被转换成表示范围更大的类型,故而把这种转换称为“升级(promotion)”。
\2. 按照从高到低的顺序给各种数据类型分等级,依次为:long double, double, float, unsigned long long, long long, unsigned long, long, unsigned int 和 int。这里有一个小小的例外,如果 long 和 int 大小相同,则 unsigned int 的等级应位于 long 之上。char 和 short 并没有出现于这个等级列表,是因为它们应该已经被升级成了 int 或者 unsigned int。
\3. 在任何涉及两种数据类型的操作中,它们之间等级较低的类型会被转换成等级较高的类型.
JDBC复习:
1.加载驱动
Class.forName("com.mysql.jbdc.Driver")//8.0之后为 "com.mysql.cj.jdbc.Driver"
2.用户信息和url
String url ="jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username="root";
String password="12345";
3.连接成功,数据库对象Connection代表数据库
Connection connection =DriverManager.getConnection(url,username,password);
4.执行SQL的对象Statement执行sql的对象
Statement statement=connection.createStatemet();
5.执行Sql的对象去执行SQL ,可能存在结果,查看返回结果
String sql="select * from users";
ResultSet resultSet=statement.executeQuery(sql);//返回的结果集,结果集中封装了我们全部的查询出来的结果
/*
或者使用 prepareStatement :
String sql = "select * from users where username=? and userpwd=?";
PreparedStatement pstm = connection.prepareStatement(sql);
pstmt.setString(1, username);//给第一个问号赋值
pstmt.setString(2, userpwd);//给第二个问号赋值
result=pstm.executeQuery();
*/
While(resultSet.next()){
System.out.println("id= "+resultSet.getObject("id"));
}
6.释放连接
resultSet.close();
statement.close();
connection.close();
并发和并行区别
并发(Concurrent)
当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。
并发,指的是多个事情,在同一段时间段内发生了,大家都在争夺统一资源。再举一个例子:我们可以拿网吧来举例子。
一个网吧每天晚上六点到晚上十点的用户量特别大,这时候可以称之为并发量大。
假如该网吧有100个机子,但是晚上六点到晚上十点却有150人来上网,这时候就有50人无法正常上网,要么该50人在此等待,要么就离开网吧。
网吧处理这个一百五十人的上网请求,不是在同一时刻进行的,而是在一段时间内处理的,其实,这就是并发。
并行
当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
我们在玩电脑的时候,计算机可以“同时”运行着音乐软件和IDEA,我们可以边敲代码,边听音乐,计算机同时的在做多件事情。
在单核cpu的计算机中,我们似乎也能“同时”做这些事情,但这不是真正意义上的并行,其底层是由于cpu快速切换执行任务,给我们一种同时运行的错觉而已。
但是,当计算机是多核cpu的时候,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这时候才是真正的“同时”进行,我们称之为并行。
就好比,一个网吧,它有多台电脑可以同时满足多位客户的上网需求,这就是并行,同时进行,互不争抢。
并发和并行的区别
并发是指一个处理器同时处理多个任务,并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
并发是逻辑上的同时发生,而并行是物理上的同时发生。
并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。
并发和并行两者的最大区别:一个是交替执行,一个是同时执行,一图胜千言,如下图所示:
摘录文档:https://mikechen.cc/15031.html
构造方法
所谓构造方法,
1,使用关键字new实例化一个新对象的时候默认调用的方法;
2,构造方法所完成的主要工作是对新创建对象的数据成员赋初值。 使用构造方法时需注意以下几点:
1.构造方法名称和其所属的类名必须保持一致;
2.构造方法没有返回值,也不可以使用void;
3.构造方法也可以像普通方法一样被重载(但不能被重写);
4.构造方法不能被static和final修饰;
5.构造方法不能被继承,子类使用父类的构造方法需要使用super关键字
高频面试题:单列模式的6种实现方式
一、单例模式的定义
定义: 确保一个类只有一个实例,并提供该实例的全局访问点。
这样做的好处是:有些实例,全局只需要一个就够了,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗费系统资源。
二、单例模式的设计要素
- 一个私有构造函数 (确保只能单例类自己创建实例)
- 一个私有静态变量 (确保只有一个实例)
- 一个公有静态函数 (给使用者提供调用方法)
简单来说就是,单例类的构造方法不让其他人修改和使用;并且单例类自己只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,只能调用。这样就确保了全局只创建了一次实例。
三、单例模式的6种实现及各实现的优缺点
类方法:做静态方法。有static
对象方法: 又叫实例方法,非静态方法 。没有static修饰
访问一个对象方法,必须建立在有一个对象的前提的基础上
访问类方法,不需要对象的存在,直接就访问
package charactor;
public class Hero {
public String name;
protected float hp;
//实例方法,对象方法,非静态方法
//必须有对象才能够调用
public void die(){
hp = 0;
}
//类方法,静态方法
//通过类就可以直接调用
public static void battleWin(){
System.out.println("battle win");
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
//必须有一个对象才能调用
garen.die();
Hero teemo = new Hero();
teemo.name = "提莫";
//无需对象,直接通过类调用
Hero.battleWin();
}
}
调用类方法
和访问类属性一样,调用类方法也有两种方式
-
对象.类方法
garen.battleWin();
-
类.类方法
Hero.battleWin();
这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。
并且在很多时候,并没有实例.如随机数获取方式
Math.random()
什么时候设计对象方法,什么时候设计类方法
如果在某一个方法里,调用了对象属性,比如
public String getName(){ return name; }
name属性是对象属性,只有存在一个具体对象的时候,name才有意义。 如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法
如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法,比如
public static void printGameDuration(){ System.out.println("已经玩了10分50秒"); }
在使用interface声明一个接口时,只可以使用( public)
ArrayList 详解:https://blog.51cto.com/u_14819316/3116907
LinkList详解: https://blog.51cto.com/u_15103042/2652527
都不是线程安全的。线程安全版本的数组容器是Vector。Vector的实现很简单,就是把所有的方法统统加上synchronized就完事了。你也可以不使用Vector,用Collections.synchronizedList把一个普通ArrayList包装成一个线程安全版本的数组容器也可以,原理同Vector是一样的,就是给所有的方法套上一层synchronized。
mybatis和hibernate的区别
MyBatis和Hibernate的对比
MyBatis与Hibernate都是对象关系映射(ORM)框架,都是用于将数据持久化的框架技术,都是对JDBC的封装。两者的区别是存在于多方面的:
1.Hibernate是全自动,而MyBatis是半自动 Hibernate完全可以自动生成SQL。而MyBatis仅有基本的字段映射,仍然需要通过手写SQL来实现和管理。
Hibernate是一个完全(标准)的ORM框架(对象关系映射),Hibernate的SQL语句是全自动生成,也就是通过JavaBean对象与数据库的映射结构来自动生成SQL语句,所以不需要写SQL语句;
而MyBatis是一个不完全的ORM框架(半ORM框架),mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理
2.Hibernate编码量小会减少开发周期,MyBatis编码量大会拖慢开发周期Hibernate是对JDBC的高度封装,使用起来几乎不用写SQL,开发的时候,会减低开发周期.MyBatis需要自己写SQL,编码量较大,会拖慢开发周期。
3.Hibernate数据库移植性远大于MyBatisHibernate通过它强大的映射结构和HQL语言,大大降低了对象与数据库(oracle、mySQL等)的耦合性,而MyBatis由于需要手写SQL,移植性也会随之降低很多,成本很高。
4.MyBatis入门简单,Hibernate入门比较难MyBatis入门简单,即学即用。Hibernate学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好Hibernate方面需要开发者的经验和能力都很强才行。
5.SQL直接优化上,MyBatis要比Hibernate方便很多
MyBatis更专注的是SQL本身,它需要写大量SQL语句。由于MyBatis的SQL都是写在xml里,因此优化SQL比Hibernate方便很多。而Hibernate的SQL很多都是自动生成的,无法直接维护SQL;总之,写SQL的灵活度上Hibernate不及MyBatis。
Hibernate适用需求变化不多的中小型项目,比如:后台管理系统,erp,orm,oa等
MyBatis适用需求变化较多的项目(大型项目),比如:互联网项目。
两者最大的区别
针对简单逻辑,Hibernate与MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法。
针对高级查询,MyBatis需要手动编写SQL语句,以及ResultMap,而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于流程。
开发难度对比
Hibernate的开发难度大于MyBatis,主要由于Hibernate比较复杂,庞大,学习周期比较长。
MyBatis则相对简单,并且MyBatis主要依赖于生气了的书写,让开发者刚进更熟悉。
sql书写比较
Hibernate也可以自己写sql来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性,不过Hibernate具有自己的日志统计。
MyBatis的sql是手动编写的,所以可以按照要求指定查询的字段,不过没有自己的日志统计,所以要借助Log4j来记录日志。
数据库扩展性计较
Hibernate与数据库具体的关联在XML中,所以HQL对具体是用什么数据库并不是很关心
MyBatis由于所有sql都是依赖数据库书写的,所以扩展性、迁移性比较差。
SpringMvc请求过程:
1、 用户向服务端发送一次请求,这个请求会先到前端控制器DispatcherServlet(也叫中央控制器)。
2、DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器。由此得知,该请求该由哪个Controller来处理(并未调用Controller,只是得知)
3、DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controller
4、HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet
5、DispatcherServlet将ModelAndView交给ViewReslover视图解析器解析,然后返回真正的视图。
6、DispatcherServlet将模型数据填充到视图中
7、DispatcherServlet将结果响应给用户
链接:https://www.nowcoder.com/questionTerminal/aded9b6a7ae84bbca563670837768dc8
来源:牛客网
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
(1)加载:容器通过类加载器使用servlet类对应的文件加载servlet
(2)创建:通过调用servlet构造函数创建一个servlet对象
(3)初始化:调用init方法初始化
(4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
(5)卸载:调用destroy方法让servlet自己释放其占用的资源
forward和redirect是什么?
是servlet的主要两种跳转方式,forward又叫转发,redirect叫重定向
区别(地址栏,数据共享,应用场景,效率,本质,次数)
1、从地址栏显示来说:forward是服务器内部重定向,
客户端浏览器的网址不会发生变化;redirect发生一个状态码,告诉服务器去重新请求那个网址,显示的的新的网址
2、数据共享:在定向过程中forward使用的是同一个request,可以共享;redirect不可以。
3、应用场景:forward一般用于用户登录:redirect用于用户注销登录返回主页面或者跳转其他页面
4、forward效率更高
5、本质上说:forward转发是服务器上的行为,而redirect是客户端行为
6、次数:forward只有一次,redirect两次
Java语言在序列化的时候不会序列化static属性
200:成功,Web 服务器成功处理了客户端的请求。
301:永久重定向,当客户端请求一个网址的时候,Web 服务器会将当前请求重定向到另一个 网址,搜索引擎会抓取重定向后网页的内容并且将旧的网址替换为重定向后的网址。
302:临时重定向,搜索引擎会抓取重定向后网页的内容而保留旧的网址,因为搜索引擎认为 重定向后的网址是暂时的。
400:客户端请求错误,多为参数不合法导致 Web 服务器验参失败。
404:未找到,Web 服务器找不到资源。
500:Web 服务器错误,服务器处理客户端请求的时候发生错误。
503:服务不可用,服务器停机。
504:网关超时。
HTTP 和 HTTPS 有什么区别?
-
端口号 :HTTP 默认是 80,HTTPS 默认是 443。
-
URL 前缀 :HTTP 的 URL 前缀是
http://
,HTTPS 的 URL 前缀是https://
。 -
安全性和资源消耗 : HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。
Cookie 的作用是什么? 和 Session 有什么区别?
Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
Cookie 一般用来保存用户信息 比如 ① 我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你把登录的一些基本信息给填了;② 一般的网站都会有保持登录,也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③ 登录一次网站后访问网站其他页面不需要重新登录。Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
Cookie 存储在客户端中,而 Session 存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密,然后使用到的时候再去服务器端解密。