Javase常见题型
1. 自增变量
public class Test {
public static void main(String[] args) {
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
System.out.println("i=" + i);
System.out.println("j=" + j);
System.out.println("k=" + k);
}
}
j=1,i=4,k=11
- i = i ++
- ①把i的值压入操作数栈
②i变量自增1
③把操作数栈中的值赋值给i
- int j = i ++
- ①把i的值压入操作数栈
②i变量自增1
③把操作数栈中的值赋值给j
- int k = i + ++i * i++
- ①把i的值压入操作数栈
②i变量自增1
③把i的值压入操作数栈
④把i的值压入操作数栈
⑤i变量自增1
⑥把操作数栈中前两个弹出求乘积结果再压入栈
⑦把操作数栈中的值弹出求和再赋值给k
赋值=,最后计算
=右边的从左到右加载值依次压入操作数栈
实际先算哪个,看运算符优先级
自增、自减操作都是直接修改变量的值,不经过操作数栈
最后的赋值之前,临时结果也是存储在操作数栈中
2. 单例设计模式
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
- 一是某个类只能有一个实例;
- 构造器私有化
- 二是它必须自行创建这个实例;
- 含有一个该类的静态变量来保存这个唯一的实例
- 三是它必须自行向整个系统提供这个实例;
- 对外提供获取该实例对象的方式:
(1)直接暴露(2)用静态变量的get方法获取
几种常见形式:
- 饿汉式:直接创建对象,不存在线程安全问题
- 直接实例化饿汉式(简洁直观)
- 枚举式(最简洁)
- 静态代码块饿汉式(适合复杂实例化)
- 懒汉式:延迟创建对象
- 线程不安全(适用于单线程)
- 线程安全(适用于多线程)
- 静态内部类形式(适用于多线程)
写一个Singleton示例
/*
* 饿汉式:
* 在类初始化时直接创建实例对象,不管你是否需要这个对象都会创建
*
* (1)构造器私有化
* (2)自行创建,并且用静态变量保存
* (3)向外提供这个实例
* (4)强调这是一个单例,我们可以用final修改
*/
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){
}
}
/*
* 枚举类型:表示该类型的对象是有限的几个
* 我们可以限定为一个,就成了单例
*/
public enum Singleton2 {
INSTANCE
}
public class Singleton3 {
public static final Singleton3 INSTANCE;
private String info;
static{
try {
Properties pro = new Properties();
pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
INSTANCE = new Singleton3(pro.getProperty("info"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Singleton3(String info){
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "Singleton3 [info=" + info + "]";
}
}
/*
* 懒汉式:
* 延迟创建这个实例对象
*
* (1)构造器私有化
* (2)用一个静态变量保存这个唯一的实例
* (3)提供一个静态方法,获取这个实例对象
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4(){
}
public static Singleton4 getInstance(){
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton4();
}
return instance;
}
}
/*
* 懒汉式:
* 延迟创建这个实例对象
*
* (1)构造器私有化
* (2)用一个静态变量保存这个唯一的实例
* (3)提供一个静态方法,获取这个实例对象
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5(){
}
public static Singleton5 getInstance(){
if(instance == null){
synchronized (Singleton5.class) {
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton5();
}
}
}
return instance;
}
}
public class TestSingleton5 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<Singleton5> c = new Callable<Singleton5>() {
@Override
public Singleton5 call() throws Exception {
return Singleton5.getInstance();
}
};
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Singleton5> f1 = es.submit(c);
Future<Singleton5> f2 = es.submit(c);
Singleton5 s1 = f1.get();
Singleton5 s2 = f2.get();
System.out.println(s1 == s2);
System.out.println(s1);
System.out.println(s2);
es.shutdown();
}
}
/*
* 在内部类被加载和初始化时,才创建INSTANCE实例对象
* 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。
* 因为是在内部类加载和初始化时,创建的,因此是线程安全的
*/
public class Singleton6 {
private Singleton6(){
}
private static class Inner{
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance(){
return Inner.INSTANCE;
}
}
3.类初始化和实例初始化
/*
* 子类的初始化<clinit>:
* (1)j = method();
* (2)子类的静态代码块
*
* 先初始化父类:(5)(1)
* 初始化子类:(10)(6)
*
* 子类的实例化方法<init>:
* (1)super()(最前) (9)(3)(2)
* (2)i = test(); (9)
* (3)子类的非静态代码块 (8)
* (4)子类的无参构造(最后) (7)
*
* 因为创建了两个Son对象,因此实例化方法<init>执行两次
*
* (9)(3)(2)(9)(8)(7)
*/
public class Son extends Father{
private int i = test();
private static int j = method();
static{
System.out.print("(6)");
}
Son(){
// super();//写或不写都在,在子类构造器中一定会调用父类的构造器
System.out.print("(7)");
}
{
System.out.print("(8)");
}
public int test(){
System.out.print("(9)");
return 1;
}
public static int method(){
System.out.print("(10)");
return 1;
}
public static void main(String[] args) {
Son s1 = new Son();
System.out.println();
Son s2 = new Son();
}
}
/*
* 父类的初始化<clinit>:
* (1)j = method();
* (2)父类的静态代码块
*
* 父类的实例化方法:
* (1)super()(最前)
* (2)i = test();
* (3)父类的非静态代码块
* (4)父类的无参构造(最后)
*
* 非静态方法前面其实有一个默认的对象this
* this在构造器(或<init>)它表示的是正在创建的对象,因为这里是在创建Son对象,所以
* test()执行的是子类重写的代码(面向对象多态)
*
* 这里i=test()执行的是子类重写的test()方法
*/
public class Father{
private int i = test();
private static int j = method();
static{
System.out.print("(1)");
}
Father(){
System.out.print("(2)");
}
{
System.out.print("(3)");
}
public int test(){
System.out.print("(4)");
return 1;
}
public static int method(){
System.out.print("(5)");
return 1;
}
}
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)(9)(3)(2)(9)(8)(7)
类初始化过程:
- 一个类要创建实例需要先加载并初始化该类
- main方法所在的类需要先加载和初始化
- 一个子类要初始化需要先初始化父类
- 一个类初始化就是执行
<clinit>()
方法
-
<clinit>()
方法由静态类变量显示赋值代码和静态代码块组成 - 类变量显示赋值代码和静态代码块代码从上到下顺序执行
-
<clinit>()
方法只执行一次
实例初始化过程:
- 实例初始化就是执行
<init>()
方法
-
<init>()
方法可能重载有多个,有几个构造器就有几个<init>
方法 -
<init>()
方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成 - 非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行
- 每次创建实例对象,调用对应构造器,执行的就是对应的
<init>
方法 -
<init>
方法的首行是super()或super(实参列表),即对应父类的<init>
方法
方法的重写Override:
- 哪些方法不可以被重写
- final方法
- 静态方法
- private等子类中不可见方法
- 对象的多态性
- 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
- 非静态方法默认的调用对象是this
- this对象在构造器或者说
<init>
方法中就是正在创建的对象
Override重写的要求:
- 方法名
形参列表
返回值类型
抛出的异常列表
修饰符
4.方法的参数传递机制
public class Exam4 {
public static void main(String[] args) {
int i = 1;
String str = "hello";
Integer num = 200;
int[] arr = {1,2,3,4,5};
MyData my = new MyData();
change(i,str,num,arr,my);
System.out.println("i = " + i);
System.out.println("str = " + str);
System.out.println("num = " + num);
System.out.println("arr = " + Arrays.toString(arr));
System.out.println("my.a = " + my.a);
}
public static void change(int j, String s, Integer n, int[] a,MyData m){
j += 1;
s += "world";
n += 1;
a[0] += 1;
m.a += 1;
}
}
class MyData{
int a = 10;
}
i = 1, srt = hello, num = 200, arr = {2,2,3,4,5}, my.a=11
方法的参数传递机制:
- 形参是基本数据类型
- 传递数据值
- 实参是引用数据类型
- 传递地址值
- 特殊的类型:String、包装类等对象不可变性
5. 迭代与递归
编程题:有n步台阶,一次只能上1步或2步,共有多少种走法?
//递归
public class TestStep{
@Test
public void test(){
long start = System.currentTimeMillis();
System.out.println(f(100));//165580141
long end = System.currentTimeMillis();
System.out.println(end-start);//586ms
}
//实现f(n):求n步台阶,一共有几种走法
public int f(int n){
if(n<1){
throw new IllegalArgumentException(n + "不能小于1");
}
if(n==1 || n==2){
return n;
}
return f(n-2) + f(n-1);
}
}
//循环迭代
public class TestStep2 {
@Test
public void test(){
long start = System.currentTimeMillis();
System.out.println(loop(100));//165580141
long end = System.currentTimeMillis();
System.out.println(end-start);//<1ms
}
public int loop(int n){
if(n<1){
throw new IllegalArgumentException(n + "不能小于1");
}
if(n==1 || n==2){
return n;
}
int one = 2;//初始化为走到第二级台阶的走法
int two = 1;//初始化为走到第一级台阶的走法
int sum = 0;
//one保存最后走一步
//two保存最后走两步
for(int i=3; i<=n; i++){
//最后跨2步 + 最后跨1步的走法
sum = two + one;
two = one;
one = sum;
}
return sum;
}
}
- 方法调用自身称为递归,利用变量的原值推出新值称为迭代。
- 递归
- 优点:大问题转化为小问题,可以减少代码量,同时代码精简,可读性好;
- 缺点:递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。
- 迭代
- 优点:代码运行效率好,因为时间只因循环次数增加而增加,而且没有额外的空间开销;
- 缺点:代码不如递归简洁,可读性好
6. 成员变量与局部变量
public class Exam5 {
static int s;//成员变量,类变量
int i;//成员变量,实例变量
int j;//成员变量,实例变量
{
int i = 1;//非静态代码块中的局部变量 i
i++;
j++;
s++;
}
public void test(int j){//形参,局部变量,j
j++;
i++;
s++;
}
public static void main(String[] args) {//形参,局部变量,args
Exam5 obj1 = new Exam5();//局部变量,obj1 i = 0, j = 1, s = 1;
Exam5 obj2 = new Exam5();//局部变量,obj1 i = 0, j = 1, s = 2;
obj1.test(10);i = 1, j = 1, s = 3;
obj1.test(20);i = 2, j = 1, s = 4;
obj2.test(30);i = 1, j = 1, s = 5;
System.out.println(obj1.i + "," + obj1.j + "," + obj1.s);
System.out.println(obj2.i + "," + obj2.j + "," + obj2.s);
}
}
obj1 i = 2, j = 1, s = 5
obj2 i = 1, j = 1, s = 5
非静态代码块的执行:每次创建实例对象都会执行
方法的调用规则:调用一次执行一次
局部变量与成员变量(类变量、实例变量)的区别:
- 声明的位置
- 局部变量:方法体{}中,形参,代码块{}中
- 成员变量:类中方法外
- 类变量:有static修饰
- 实例变量:没有static修饰
- 修饰符
- 局部变量:final
- 成员变量:public、protected、private、final、static、volatile、transient
- 值存储的位置
- 局部变量:栈
- 实例变量:堆
- 类变量:方法区
- 作用域
- 局部变量:从声明处开始,到所属的}结束
- 实例变量:在当前类中“this.”(有时this.可以缺省),在其他类中“对象名.”访问
- 类变量:在当前类中“类名.”(有时类名.可以省略),在其他类中“类名.”或“对象名.”访问
- 生命周期
- 局部变量:每一个线程,每一次调用执行都是新的生命周期
- 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
- 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的
当局部变量与xx变量重名时,如何区分:
- 局部变量与实例变量重名
- 在实例变量前面加“this.”
- 局部变量与类变量重名
- 在类变量前面加“类名.”