java
依据个人认知总结,如有技术性问题烦请指正!
java基础
数据类型
基本数据类型
基本数据类型的存储原理: 所有的简单数据类型不存在“引用”的概念,基本数据类型都是直接存储在内存中的内存栈上的,数据本身的值就是存储在栈空间里面,而Java语言里面八种基本数据类型就是这种存储模型
基本类型是按值传递
一个字节等于8位
byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0
short:短整型,在内存中占16位,即2个字节,取值范围-32768~32717,默认值0
int:整型,在内存中占32位,即4个字节,取值围-2147483648~2147483647,默认值0
long:长整型,在内存中占64位,即8个字节,取值范围-263~263-1,默认值0L
float:单精度浮点型,在内存中占32位,即4个字节,用于存储带小数点的数字
(与double的区别在于float类型有效小数点只有6~7位),默认值0.0f
double:双精度浮点型,用于存储带有小数点的数字,在内存中占64位,即8个字节,默认值0.0d
char:字符型,用于存储单个字符,在内存中占16位,即2个字节,取值范围0~65535,默认值是\u0000 即空值
boolean:布尔类型,在内存中占8位,即1个字节,用于判断真或假,默认值false
练习题
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
- 第一个有错,1是int类型,不能转为short,第二个对,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
- 表达式的数据类型自动提升, 关于类型的自动提升,注意下面的规则。
①所有的byte,short,char型的值将被提升为int型;
②如果有一个操作数是long型,计算结果是long型;
③如果有一个操作数是float型,计算结果是float型;
④如果有一个操作数是double型,计算结果是double型;
而声明为final的变量会被JVM优化
引用类型
首先我们要知道,引用类型的出现是为了节省内存,当我们使用引用类型时,一定要给定一个空间,
即需要new一个对象。
(1) 引用是一种数据类型(保存在栈中),保存了对象在内存(堆)中的地址,这种类型即不是我们平时所说的基本数据类型也不是类实例(对象);
(2) 不同的引用可能指向同一个对象,换句话说,一个对象可以有多个引用,即该类类型的变量。
Integer
integer的128陷阱
算法 IP地址和int的双向转换
192.168.1.1如何转换成int类型?int类型如何转换成IP地址?用位运算解决
关键字
static-静态属性
static关键字并不会改变变量和方法的访问权限
static是不允许用来修饰局部变量
所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)
静态变量
静态变量(带有static关键字的字段)是属于类的,所有该类的对象共用该字段;
非静态变量(普通字段)是属于类的对象的,每一个该类的对象都有自己的非静态字段,他们互不影响。
静态方法
静态方法与普通方法的区别,与静态字段与普通字段的区别类似
静态方法是不在对象上执行的方法,在调用静态方法时,不需要实例化该类而调用普通方法必须实例化该类。
abstract-抽象
抽象类
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用
抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口
抽象方法
抽象方法只包含一个方法名,而没有方法体
声明抽象方法会造成以下两个结果:
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
抽象类总结规定
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
为什么需要抽象类?
抽象方法和抽象类看上去是多余的,对于抽象方法,不知道如何实现,定义一个空方法体不就行了吗,而抽象类不让创建对象,看上去只是增加了一个不必要的限制。
引入抽象方法和抽象类,是Java提供的一种语法工具,对于一些类和方法,引导使用者正确使用它们,减少被误用。
使用抽象方法,而非空方法体,子类就知道他必须要实现该方法,而不可能忽略。
使用抽象类,类的使用者创建对象的时候,就知道他必须要使用某个具体子类,而不可能误用不完整的父类。
无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。
extends-继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
继承的特性
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)
多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作
多态的优点
消除类型之间的耦合关系
可替换性
可扩充性
接口性
灵活性
简化性
多态存在的三个必要条件
继承
重写
父类引用指向子类对象:Parent p = new Child();
多态的实现方式
方式一:重写:
重写(Override):子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常
注意区分重载(重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。)
方式二:接口
1.生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
2.java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。
方式三:抽象类和抽象方法
interface-接口
接口,在JAVA中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
Reflection-反射
Java的反射是指程序在运行期可以拿到一个对象的所有信息,是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3In4YVW-1657867343290)(D:\资料留存\面试\图片知识点\反射API.jpg)]
final
final 用于修饰变量、方法和类。
final 变量:被修饰的变量不可变,不可变分为引用不可变和对象不可变,final 指的是引用不可变,final 修饰的变量必须初始化,通常称被修饰的变量为常量。
final 方法:被修饰的方法不允许任何子类重写,子类可以使用该方法。
final 类:被修饰的类不能被继承,所有方法不能被重写。
finally
finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0) 可以阻断 finally 执行。
finalize
是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。
一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。
implements
类使用implements关键字实现接口
super
super关键字是一个引用变量,用于引用直接父类对象。
每当创建子类的实例时,父类的实例被隐式创建,由super关键字引用变量引用。
调用的位置只能在构造器的第一行
super关键字的用法如下:
super可以用来引用直接父类的实例变量。
super可以用来调用直接父类方法。
super()可以用于调用直接父类构造函数。
使用super和this应注意:
1)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
2)super()和this()类似,区别是,super从子类中调用父类的构造方法,this()在同一类内调用其它方法。
3)super()和this()均需放在构造方法内第一行。
4)尽管可以用this调用一个构造器,但却不能调用两个。
5)this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
6)this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
7)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
访问修饰符
private | 私有的 | 被其修饰的属性以及方法只能被该类的对象 访问,其子类不能访问,更不能允许跨包访问 |
protected | 受保护访问 | 被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问 |
public | 公开的 | 被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问 |
default(不加任何访问修饰符) | 默认访问 | 只允许在同一个包中进行访问 |
类内部成员初始化顺序
方法调用
有静态方法才能被类名调用
一般方法,只能被实例对象调用
final修饰方法,只是说明本方法不能被重写
abstract修饰方法,表示本方法为抽象方法,没有方法体,且抽象方法必须在抽象类中,但是抽象类中可以没有抽象方法
final语义:
在接口里面的变量默认都是public static final 的,它们是公共的,静态的,最终的常量.相当于全局常量,可以直接省略修饰符。
实现类可以直接访问接口中的变量
变量被static修饰则该变量为类变量,类变量存储在方法区,不属于每个实例的私有,该类的所有对象操作的都是同一个变量
String、StringBuilder、StringBuffer
执行效率(相对情况):StringBuilder > StringBuffer > String
String:适用于少量的字符串操作的情况,对象不可变(内部结构用的是private final char[],字符串拼接是生成新的字符串)
StringBuilder:线程不安全,性能较好,适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:线程安全(StringBuffer中的方法基本被synchronized修饰),性能较差,适用多线程下在字符缓冲区进行大量操作的情况,对象可变(字符串拼接是在原字符串后链接)
问题1、String, StringBuilder, StringBuffer的区别、使用场景
String数据内部结构用的是private final char[],是不可变的
其实被private final修饰的是引用对象,只要引用不变,char[]数组中的内容是可以变的,但是String没有提供修改方法,所以String是不可变的。但是,可以用反射把value的访问权限valueField.setAccessible(true),那么外部就可以对value进行修改了。
对String进行 “+=”操作 时,会生成一个新的String对象,让引用指向新的对象。如果经常改变String内容的场景,不要用String类型,内存中无引用的对象多了,容易造成内存泄漏,GC就开始工作,性能会降低。
使用String类的concat与replace方法时,不会对原来的对象产生影响,他们会返回一个全新的对象
StringBuilder, StringBuffer的实现原理:
• 用char[]存储数据,当char[]盛不下时,进行扩容,2倍扩容,避免总是扩容,默认数组的长度是16,如果能够预测char[]的长度的话,如果长度小于16,那么可以不设置,如果比16长的话,应该尽量设置长度,不然的话,会进行扩容,导致性能降低。
java异常处理机制
异常处理一般格式:
捕获异常:
try{ //代码块 }catch(异常类型,例如:Exception e){ //需要抛出的异常,例如:e.printStackTrace(); }catch(异常类型){ //需要抛出的异常 }finally{ //必定执行的代码块 }
所以说在一个异常处理中catch语句块是可以多个的,也就是可以抛出多个异常!