java基础笔记一
java面向对象基础
面向对象和面向过程的区别:
面向对象:解决一个问题,将解决问题的步骤罗列出来,将解决每一个问题的步骤都抽象出来一个对象处理;
面向过程:解决一个问题,按照解决问题的一个个步骤走,判断执行解决这个问题;
面向过程:开始—黑走—棋盘—判断—白走—棋盘—判断—循环。只需要关注每一步怎么实现即可.
面向对象的三大基本特征:
封装:一个类里面封装了数据及操作这些数据的方法;
继承:提高了代码的复用性;分为类继承和接口继承,
类继承:子类继承基类,实现继承是指直接使用基类的属性和方法而无需额外编码的能力;
接口继承:子类继承接口,接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
多态:所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。(编译看左边,运行看右边)
public class person{
public void run(){
System.out.println("person class");
}
}
public class student extends person{
现在流行的说法有静态多态和动态多态:静态多态:一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法;动态多态:在运行期间才决定调用哪个方法
方法的重写(Overriding)和重载(Overloading):
重载:方法名相同,方法参数不同,返回值和修饰符不同不能作为方法重载的必然条件;
重写:Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法
重写VS重载
1、重载是一个编译期概念、重写是一个运行期间概念。
2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。
3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法
4、因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)
重写的条件
参数列表必须完全与被重写方法的相同;
返回类型必须完全与被重写方法的返回类型相同;
访问级别的限制性一定不能比被重写方法的强;
访问级别的限制性可以比被重写方法的弱;
重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明)
不能重写被标示为final的方法;
如果不能继承一个方法,则不能重写这个方法。
重载的条件
被重载的方法必须改变参数列表;
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
java的继承与实现
继承(extends):当多个类中有可复用的部分,可将可复用的部分抽象为一个类,让他们都继承这个类
实现(implements):如果多个类处理的目标都是一样,但是处理的方式不同,那么就定义一个接口(标准),让他们实现这个接口,各自对实现的目标做具体的处理
继承主要是复用,而接口则是定义标准;
继承只能单继承,接口可以多实现,接口不能实现接口不能继承类;
在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法);而在继承中可以定义属性方法,变量,常量等。
构造函数和变量和访问修饰符
构造函数和默认构造函数:
构造函数属于一种特殊的方法,主要用来在创建对象时,对对象进行初始化,及对成员变量赋值;
一个类可以有多个构造函数,用不同的参数个数和参数的数据类型不同来区别方法的重载
接口
-
接口用interface关键字修饰;
-
实现接口要重写接口中的所有方法,接口不能被实例化,接口中没有构造方法
-
接口中的所有方法都默认用public abstract修饰
-
接口中的成员变量都默认用public static final修饰
-
implements可以实现多个接口
变量
public class Demo01{
private String a="我的世界";
public static String b="他的世界";
public void test01(String c){
System.out.println(c);
}
}
a==成员变量
b==静态变量
c==局部变量
静态变量 static详解:
可以直接使用类名.静态变量或静态方法使用;(生命周期和类相同,和类一起加载)
虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。
public class Student{
public static String name; //姓名
public Integer age;
public void run(){
System.out.println("奔跑!!");
}
}
static和构造方法加载顺序面试题
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
输出结果为
test static myclass static person static person Test test constructor person MyClass myclass constructor
解释
-
找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
-
加载Test类的时候,发现Test类有static块,而是先执行static块,输出test static结果
-
然后执行new MyClass(),执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,而是要先加载Test类,Test类之前已加载
-
加载MyClass类,发现MyClass类有static块,而是先执行static块,输出myclass static结果
-
然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,而是执行Person person = new Person("Test")代码,发现Person类没有加载
-
加载Person类,发现Person类有static块,而是先执行static块,输出person static结果
-
接着执行Person构造器,输出person Test结果
-
然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了
-
再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果
-
最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了
访问修饰符
public :当前类,子类,同一个项目下的所有包都可以访问;
private:当前类中的方法函数可以访问;
protected:当前类、子类、同一包下面的所有类都可以访问;
default:当前类、同一包下的类可以访问;
形参与实参
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
public static void main(String[] args){
}
main方法中的数组就是形参,将String[]
java中的值传递和引用传递讨论
public static void main(String[] args) {
Test pt = new Test();
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
pt.pass(hollis);
System.out.println("print in main , user is " + hollis);
}
public void pass(User user) {
user.setName("hollischuang");
System.out.println("print in pass , user is " + user);
}
上面代码输出结果为:
print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='hollischuang', gender='Male'}
值传递和共享对象传递
JAVA基础知识
数据类型
一字节等于8bit
整形:
byte: 一个字节 范围为10000000(-128)——01111111(127)
short: 二个字节 范围为-32,768——32767
int: 四个字节 范围为-2,147,483,648 ——2,147,483,647
long: 八个字节 范围为-9,223,372,036,854,775,808 ——9,223,372,036, 854,775,807
超出范围则会发生溢出,这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。所以,在程序中使用同类型的数据进行运算的时候,一定要注意数据溢出的问题
浮点型:
float: 单精度浮点数,四个字节
double:双精度浮点数,八个字节
由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。建议使用BigDecimal或者Long(单位为分)来表示金额。
字节型:
char:二个字节
布尔型:
boolean:一位(bit):0或1
基本类型的好处
我们都知道在Java语言中,
new一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个Java对象的话,就会比较笨重。所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
包装类型
| 基本数据类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
为什么需要包装类型
因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。
为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
自动装箱和自动拆箱
将基本数据类型变成包装类即为装箱,将包装类变为基本数据类型即为拆箱;
public static void main(String[] args){
Integer integer = 1; //自动装箱
int i = integer; //自动拆箱
}
//将代码反编译后的结果
public static void main(String[] args){
Integer integer = Integer.valueOf(1);
int i = integer.intValue();
}
自动装箱都是通过包装类的
valueOf()方法来实现的.自动拆箱都是通过包装类对象的xxxValue()来实现的。
自动拆箱装箱的场景
场景一、将基本类型放入集合类
我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢?
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i ++){
li.add(i);
}复制ErrorOK!
将上面代码进行反编译,可以得到以下代码:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2){
li.add(Integer.valueOf(i));
}复制ErrorOK!
以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。
场景二、包装类型和基本类型的大小比较
有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码:
Integer a=1;
System.out.println(a==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool?"真":"假");复制ErrorOK!
对以上代码进行反编译,得到以下代码:
Integer a=1;
System.out.println(a.intValue()==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool.booleanValue?"真":"假");复制ErrorOK!
可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码:
Integer a=1;
System.out.println(a==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool?"真":"假");复制ErrorOK!
对以上代码进行反编译,得到以下代码:
Integer a=1;
System.out.println(a.intValue()==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool.booleanValue?"真":"假");复制ErrorOK!
可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
场景三、包装类型的运算
有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢?看以下代码:
Integer i = 10;
Integer j = 20;
System.out.println(i+j);复制ErrorOK!
反编译后代码如下:
Integer i = Integer.valueOf(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());复制ErrorOK!
我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。
场景四、三目运算符的使用
这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码:
boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;复制ErrorOK!
很多人不知道,其实在int k = flag ? i : j;这一行,会发生自动拆箱。反编译后代码如下:
boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;
System.out.println(k);复制ErrorOK!
这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。
因为例子中,flag ? i : j;片段中,第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候i的值为null,那么就会发生NPE。(自动拆箱导致空指针异常)
场景五、函数参数与返回值
这个比较容易理解,直接上代码了:
//自动拆箱
public int getNum1(Integer num) {
return num;
}
//自动装箱
public Integer getNum2(int num) {
return num;
}
在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
适用于整数值区间-128 至 +127。
只适用于自动装箱。使用构造函数创建对象不适用。
包装类的比较用equals方法,防止缓冲导致比较异常
自动拆装箱带来的问题
包装对象的数值比较,不能简单的使用
==,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals比较。前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。
如果一个for循环中有大量拆装箱操作,会浪费很多资源。
如何正确定义接口的返回值(boolean Boolean)类型及命名(succes issuccess))
String
创建String方式:
String str = "我的世界";
String str1 = new String("我的世界");
字符串比较用equals
一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。
String、StringBuffer和StringBuilder的区别
1. 对象的可变与不可变 String对象不可变,StringBuffer和StringBuilder对象可变。
2. 线程是否安全
-
String中的对象是不可变的,也就可以理解为常量,所以线程安全。
-
StringBuffer中的方法大都采用了synchronized关键字修饰,所以是线程安全的。
-
StringBuilder没有对方法进行加同步锁,所以是非线程安全的。
3. 字符串追加速度比较 StringBuilder > StringBuffer > String
4. StringBuffer和StringBuilder的共同点
-
StringBuffer和StringBuilder有公共的抽象父类AbstractStringBuilder
-
抽象类与一个接口的区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。
-
StringBuilder和StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加上synchronized关键字,进行同步。 如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。
总结一下: String:适用于少量的字符串操作的情况 StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况 StringBuffer:适用于多线程下在字符缓冲区进行大量操作的情况

浙公网安备 33010602011771号