Java基础复习-1
本文仅对学习过程中所缺java知识点的查缺补漏复习
合法标识符规则
- 由26个英文字母大小写,0-9,_或$组成
- 数字不可以开头
- 不可以使用关键字和保留字,但能包含关键字和保留字
- Java中严格区分大小写,长度无限制
- 标识符不能包含空格
Java中名称命名规范
- 包名:多单词组成时所有字母都小写:xxxyyyzzz
- 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
- 常量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
- 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
变量
分类-按数据类型
整型
- Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保证java程序的可移植性;
- java的整型常量默认为
int
型,声明long型常量后须加L
或l;
- java程序中变量默认声明为
int
型,除非不足以表示较大的数,才用long
;
类型 | 占用存储空间 | 范围 |
---|---|---|
byte | 1字节=8bit | -128 到127 |
short | 2字节 | -2^15 到2^(15)-1 |
int | 4字节 | -2^31 到2^(31)-1 |
long | 8字节 | -2^63 到2^(63)-1 |
浮点型
- 浮点型常量有两种表示形式:
- 十进制数:
5.12
、512.0f
、.512
(必须要有小数点) - 科学计数法:
5.12e2
、512E2
、100E-2
- 十进制数:
- float:单精度,尾数可以精确到7位有效数字;
- double:双精度,精度是float的两倍;
- Java的浮点型常量默认为double型,声明成float型,需要在数字后加
f
或F
;
类型 | 占用存储空间 | 范围 |
---|---|---|
float | 4字节 | -3.403E38 到3.403E38 |
double | 8字节 | -1.798E308 到1.798E308 |
float表示的范围比long还大
字符型
- char型数据用来表示通常意义上字符(2字节);
- Java中的所有字符都是用Unicode编码,所以一个字符可以存储一个字母,一个汉字或者其他书面语的一个字符;
- 三种表示形式:
- char c = ‘a’;
- char c = ‘\n’;
- char c = ‘\u000a’;
- char类型可以进行运算;
基本数据类型运算
2个特性:
1.自动类型提升:当表示范围小的数据类型的变量与表示范围大的数据类型变量做运算时,结果自动提升为范围大的数据类型:int、long、float、double(表示范围依次递增),特别的,byte、short、char的运算结果为int型;
byte a = 1;
byte b = 2;
int c = a + b;
double d = 3.0;
double e = c + d;
2.强制类型转换:自动类型提升的逆运算;(可能会出现精度损失)
float a = 3.1f;
int b = (int)a;
引用类型
String
可以和8种基本数据类型进行+运算,结果为String
String str = "hello";
int num = 1;
char c = 'a'; //97
System.out.println(str + num + c);
System.out.println(str + (num + c));
System.out.println(num + str + c);
System.out.println(c + str + num);
结果
hello1a
hello98
1helloa
ahello1
进制转换
- 二进制(binary):以
0b
或0B
开头 - 八进制(octal):以数字
0
开头 - 十进制(decimal)
- 十六进制(hex):以
0x
或0X
开头
计算机底层用二进制补码形式保存所有整数
- 原码:直接将一个数转换成二进制,最高位是符号位;
- 负数反码:对原码除符号位以外的位按位取反;
- 负数补码:负数的反码+1;
- 正数:三码合一;
运算符
short a = 1;
a += 1; //运算结果会保持变量a原本的数据类型
a = a + 1; //运算结果不会保持变量a原本的数据类型,编译失败
int i = 1;
i += 0.1; //i会保持原来的数据类型
System.out.println(i); //1
i++;
System.out.println(i); //2
int n = 10;
n += (n++) + (++n); //10 + 10 + 12
System.out.println(n); //32
instanceof:检查是否是类的对象,例子:“Hello” instanceof String:true
位运算符
位运算符是操作二进制数
运算符 | 运算 | 范例 | 解释 |
---|---|---|---|
<< | 左移 | 3 << 2 = 12:3*2*2=12 | 空缺位补0,被移除的高位丢掉 |
>> | 右移 | 3 >> 1 = 1:3/2=1 | 被移位最高位为0,右移后,空缺位补0;若最高位为1,右移后空缺位补1 |
>>> | 无符号右移 | 3 >>> 1 = 1:3/2=1 | 被移位二进制最高位无论是0还是1,右移后空缺位都补0 |
& | 与运算 | 6 & 3 = 2 | 二进制位进行&运算,只有1&1结果为1 |
| | 或运算 | 6 | 3 = 7 | 二进制位进行 | 运算,只有0 | 0结果为0 |
^ | 异或运算 | 6 ^ 3 = 5 | 相同二进制位进行 ^ 运算,结果为0,不同才为1 |
~ | 取反运算 | ~6 = -7 | 各二进制数按补码各位取反 |
运算符优先级
程序流程控制
- 顺序结构
- 分支结构
- 循环结构
数组
一维数组
int[] ids; //声明
//1.静态初始化
ids = new int[]{100,101,102};
//2.动态初始化
String[] names = new String[5];
数组元素默认初始化值:
- 整型:0
- 浮点型:0.0
- char型:0或’\u0000’,而不是’0’
- boolean型:false
- 引用类型:null
二维数组
//静态初始化
//arr放栈里,new出来的数据放堆里
int[][] arr = new int[][]{{1,2,3},{4,5},{6,7,8}};
/*
动态初始化1
外层元素初始化值:地址值
内层元素初始化值:与一维数组初始化一样
*/
String[][] arr1 = new String[3][2];
/*
动态初始化2
外层元素初始化值:null
内层元素初始化值:不能调用
*/
int[][] arr2 = new int[4][];
算法五大特性
特性 | 描述 |
---|---|
输入(Input) | 有0个或多个输入 |
输出(Ouput) | 有1个或多个输出 |
有穷性(有限性) | 算法在有限步骤后自动结束,并且每一步都要在可接受时间内完成 |
确定性(明确性) | 算法中每一步都有确定的含义,不会出现二义性 |
可行性(有效性) | 算法中每一步都是清除且可行的 |
数组中常见的异常
数组越界,空指针
面向对象三大特征
封装、继承、多态
类包括了:属性,方法
JVM内存结构
- 虚拟机栈:平时所说的栈结构,存储了局部变量;
- 堆:new出来的结构(比如:数组、对象以及对象的属性(非static))加载在堆空间;
- 方法区:类的加载信息、常量池、静态域;
重载
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
特点:
与返回值类型无关,只看参数列表,参数列表必须不同。
//下面两个方法功能一样,但是形参不同
//可变参数形参
public void show(String ... str){
}
public void show(String[] str){
}
修饰符
修饰符 | 类内部 | 同一包 | 不同包的子类 | 同一工程 |
---|---|---|---|---|
private | 是 | |||
缺省 | 是 | 是 | ||
protected | 是 | 是 | 是 | |
public | 是 | 是 | 是 | 是 |
构造器
一旦显示定义了构造器,那么系统不再提供默认无参构造器
重写
在子类中可以根据需要对从父类中继承的方法进行改造,在程序执行时,子类方法会覆盖父类方法。
要求
- 子类重写的方法必须和父类被重写方法具有相同的方法名称和参数列表;
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型;
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限;
- 子类不能重写父类中声明为private权限的方法;
- 子类方法抛出的异常不能大于父类被重写方法的异常;
注意:static
修饰的方法是属于类的,子类无法重写static修饰的方法。
super调用构造器
- 可以在子类的构造器中显式使用 super(形参列表) 的方式,调用父类中声明的指定的构造器;
- super(形参列表) 的使用,必须声明在子类构造器的首行;
- 在类的构造器中,针对this(形参列表) 或 super(形参列表) 只能二选一,不能同时出现;
- 在构造器首行,没有显式声明this(形参列表)或super(形参列表),则默认调用的是父类中的空参构造器:super();
子类对象实例化的全过程
- 从结果上来看(继承性):子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上来看:通过子类的构造器创建子类对象时,一定会直接或间接调用其父类的构造器,进而调用父类的构造器,直到调用了
java.lang.Object
类中无参构造器为止。正因为加载过所有父类的结构,所以可以看到内存中有父类的结构,子类对象才可以进行调用。 - 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new出来的子类对象。
多态
多态性,是面向对象中最重要的概念,在Java中的体现(运行时行为):
对象的多态性:父类引用指向子类对象:可以直接应用在抽象类和接口上;
多态的使用:当调用子父类同名同参数的方法时,实际上调用的是子类重写后的方法。
//这里声明的是Person的引用,指向了子类Man对象。
Person p = new Man();
//如果下面这里要调用方法的话,只能调用Person类里有的方法,不能调用到Man里的方法,否则编译不通过;但是执行的时候执行的是子类重写的方法。
对象的多态性,只适用于方法,不适用于属性;
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不
同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了
不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类
和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为早绑定或静态绑定;
而对于多态,只有等到方法调用的那一刻,编译器才会确定所要调用的具体方法,
这称为晚绑定或动态绑定;
a instanceof A; //如果对象a是类A的实例,返回true,否则返回false
//向下转型时可以用来先判断在做操作,防止运行时出现类型强转异常
//编译通过,运行不通过
Person p1 = new Woman();
Man m1 = (Man)p1;
//编译通过,运行不通过
Person p2 = new Person();
Man m2 = (Man)p2;
//编译运行都通过
Object obj = new Woman();
Person p3 = (Person)obj;
//上面两个不通过的例子是因为最开始new出来的对象不包含后面强转时新对象的方法
自动装箱和自动拆箱
//自动装箱:不用调用包装类的构造器就可以将基本数据类型赋值给包装类
int i = 1;
Integer num = i;
//自动拆箱:不用调用特定方法就能将包装类赋值给基本数据类型
int n = num;
- 基本数据类型---->包装类:自动装箱
- 包装类---->基本数据类型:自动拆箱
- 基本数据类型---->String类:
String的ValueOf()
方法 - String类---->基本数据类型:调用相应包装类的
parseXxx(String str)
方法 - 包装类---->String类:包装类对象的
toString()
方法 - String类---->包装类:new出包装类对象,带String形参
static
我们编写一个类的时候,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时希望无论是否产生了对象或无论产生了多少个对象,某些特定的数据在内存空间里只有一份。
- 使用static修饰属性,称为静态变量(类变量);没有用static修饰的变量称为非静态变量(实例变量);
- 静态变量:当我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过的;
- 实例变量:我们创建了类的多个对象,每个对象都独立地拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值修改;
- 其他说明:
- 静态变量随着类的加载而加载;
- 静态变量的加载早于对象的创建;
- 由于类只会加载一次,所以静态变量在内存中只会存在一份,存在方法区的静态域中;
- 使用static修饰的方法,称为静态方法;
- 静态方法随着类的加载而加载;
- 在静态方法中,只能调用静态的方法或属性;(生命周期一样才可以调用)
- 在非静态方法中,既可以调用非静态方法或属性,也可以调用静态方法或属性;
- 注意点:
- 在静态方法内,不能使用
this
和super
关键字; - 关于静态属性和静态方法的使用,需要从生命周期角度去理解;
- 在静态方法内,不能使用
单例模式
//饿汉式:线程安全
public class A{
//1.构造函数私有化
private A(){}
//2.new一个静态对象实例
static A a = new A();
//3.提供静态方法供外部调用
public static A getInstance(){
return a;
}
}
//懒汉式:线程不安全
public class B{
//1.构造函数私有化
private B(){}
//2.声明一个静态对象引用
static B b = null;
//3.提供静态方法供外部调用
public static B getInstance(){
if(b == null)
b = new B();
return b;
}
}
单例模式应用场景
代码块
- 静态代码块:
- 内部可以有输出语句;
- 随着类的加载而执行,而且只执行一次;
- 作用:初始化类信息;
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行;
- 静态代码块执行要优于非静态代码块;
- 静态代码块内只能调用静态属性、方法,不能调用非静态结构;
- 非静态代码块:
- 内部可以有输出语句;
- 随着对象的创建而执行;
- 每创建一个对象执行一次;
- 作用:可以在创建对象时,初始化对象属性;
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行;
- 非静态代码块内可以调用静态属性、方法,也可以调用非静态属性和方法;
- 注意:
- 代码块会优于构造器执行,如果子类继承了父类,new子类对象时,会先执行父类静态代码块,再执行子类静态代码块;然后执行父类非静态代码块,接着执行子类非静态代码块;然后再执行父类构造器,最后执行子类构造器。
- main方法虽然是程序人口,但同时也是一个普通静态方法,所以会慢于静态块的执行;
对象赋值顺序
- 默认初始化
- 显示初始化 / 在代码块中赋值
- 构造器中初始化
- 有了对象以后,通过操作对象赋值
final
- final修饰类:此类不能被继承
- final修饰方法:此方法不能被重写
- final修饰变量:赋值位置:显式初始化,代码块中初始化、构造器中初始化
- 注意:当final修饰形参,且形参是类时,形参的属性可以修改,但是不可以让形参指向新对象
抽象类
abstract修饰类:抽象类
- 此类不能被实例化;
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程);
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作;
abstract修饰方法:抽象方法
- 抽象方法只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类;反之,抽象类中可以没有抽象方法;
- 子类重写了父类中所有的抽象方法后,子类才可实例化;
- 如果子类没有重写父类中所有的抽象方法,那么子类也是一个抽象类,需要用abstract修饰;
abstract使用注意点:
- abstract不能修饰:属性、构造器、私有方法、静态方法、final方法、final类
继承
继承的好处:
- 减少代码冗余,提高代码复用性;
- 便于功能扩展;
- 为多态使用提供前提;
匿名子类
//Person是一个抽象类,下面的做法是创建一个抽象类的匿名子类
Person p = new Person(){
//重写方法
};
//或者
method(new Person(){
//重写方法
});
接口
- 接口中不能定义构造器,接口不可以实例化;
- 接口和接口之间可以继承;
- 接口也有匿名实现类;(具体参考抽象类部分)
- 接口的变量默认自带public、static、final;
- 接口中可以添加抽象方法、静态方法和默认方法;
- 静态方法可以通过接口直接调用;
- 默认方法用default修饰
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。---->类优先原则;
- 如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法,阿么在实现类没有重写这些方法,将会报错---->接口冲突;这时需要在实现类中重写这些方法(重写完调用的是重写后的);
代理模式
public class NetWorkTest{
public static void main(String[] args){
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browser();
}
}
//接口
interface NetWork{
public void browser();
}
//被代理类
class Server implements NetWork{
@Override
public void browser(){
System.out.println("真实服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
public void browser(){
check();
work.browser();
}
}
代理模式应用场景
内部类
- Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类;
- 内部类分类:成员内部类(静态和非静态)vs 局部内部类(方法内、代码块内、构造器内);
成员内部类:
- 一方面,作为外部类的成员,可以
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
- 另一方面,作为一个类:
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,不是用final可以被继承
- 可以被abstract修饰
常用方式
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
class MyComparable implements Comparable{
@Override
public int compareTo(Object o){
return 0;
}
}
return new MyComparable();
}
//还可以用匿名内部类方式
异常处理
在Java语言中,将程序执行中发生的不正常情况称为异常。(开发过程中的语法错误和逻辑错误不是异常)
异常分类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:
StackOverflowError
和OOM
。一般不编写针对性的代码进行处理。 - Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
常见异常:
- 编译时异常(checked)
IOException
FileNotFoundException
- 运行时异常(unchecked)
NullPointerException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException
InputMismatchException
ArithmeticException
注意:
- catch中的异常类型如果有子父类关系,那么子类需先catch;
- 常见异常对象处理方式:
String getMessage()
printStackTrace()
- 在try结构中声明的变量,出了try结果不能再被调用;
- finally中声明的是一定会被执行的代码。即使catch中又出现了异常、try中或者catch中有return语句等情况;
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动进行资源释放,此时资源释放就要声明在finally中;
- 还可以手动抛出异常(自定义异常类,然后自己new异常对象抛出);
- 自定义异常类步骤:
- 继承于现有的异常结构:RuntimeException、Exception
- 提供全局变量:serialVersionUID
- 提供重载的构造器
体会:
- 使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错,相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现;
- 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对编译时异常,一定要考虑异常处理。
- 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常;如果父类没抛,那么子类不能抛;
- 不管try中throw了异常或者有return语句,都会先执行try后带的finally语句;
面试题
1
//下面两种输出一样吗?
//1
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); //输出1.0,因为上面的三元运算符在编译的时候要求统一数据类型,=右边要提升到最高数据类型,也就是double,与true无关
//2
Object o2;
if(true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2); //输出1
2
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //false
Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); //false
//第一个是因为new出来的用==比较的结果不是指向同个地址,所以是false
//第2个和第3个,Integer类源码中设了一个缓存区,存了-128到+127范围的常用整数,如果用到了是直接从那个内存区里取,不是new,所以第二个判断结果都是同一个地址,所以是true,而第3个因为128超出了范围,所以每次都是new出来的,也就是地址每次都不同;
3
最高效方式计算2*8
,用2<<3
或8<<1
4
Java里方法的参数传递方式只有一种:值传递。即将实际参数值的副本传入方法内,而参数本身不受影响;
- 形参是基本数据类型:将实参基本数据类型变量的数据值传递给形参;
- 形参是引用数据类型:将实参引用数据类型变量的地址值传递给形参;
其他
类似:
- throw和throws:throw是抛出异常,throws是在方法名后声明该方法可能会抛出什么异常;
- Collection和Collections
- String、StringBuffer、StringBuilder
- ArrayList、LinkedList
- HashMap、LinkedHashMap
- 重写、重载
结构不相似:
- 抽象类、接口
- ==、equals()
- sleep()、wait()