avatar

Java基础知识回顾

面向对象三大特性

封装

隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。

继承

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法
子类拥有父类的非private属性,方法
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
子类可以用自己的方式实现父类的方法
java的继承是单继承
关键字:extends

多态

多态是同一个行为具有多个不同表现实行或形态的能力
多态存在的三个必要条件:

  • 继承
  • 重写
  • 父类引用指向子类对象
class Animal{
public int month = 2;
public void eat(){
System.out.println("动物吃东西");
}

}
class Dog extends Animal{
public int month = 3;

public void eat() {
System.out.println("小狗吃肉");
}

public void sleep() {
System.out.println("小狗睡午觉");
}
}
class Cat extends Animal{
public int month = 4;

public void eat() {
System.out.println("小猫吃鱼");
}
}
public class Test {
public static void main(String[] args){
Animal a = new Dog();
Animal b = new Cat();
a.eat();
a.sleep();//指定了父对象没有的函数,会报错,因为在编译的时候a为Animal对象
System.out.println(a.month);
b.eat();
System.out.println(b.month);
}
}

这一段表示创建父对象动物,狗和猫继承自动物,那么变量a和变量b分别表现为狗,猫,这就是多态。
多态中成员的特点:

  • 多态成员变量:编译运行看左边
  • 多态成员方法:编译看左边,运行看右边
    instanceof关键字用于判断某个对象是否属于某种数据类型,使用方法如下:
Animal f1=new Dog();
Animal f2=new Cat();
if(f1 instanceof Animal)//用这个关键字检测父子对象都是对的
{
System.out.println("f1是Animal的类型");
}
else{
System.out.println("f1是Dog的类型");
}

多态的方法执行是覆盖的,如果被调用的子类有这个方法则调用子类的方法,如果子类没有该方法会去父类里面找。
对于属性,使用的是同名属性隐藏机制来实现多态的,同名属性隐藏机制是指:在具有父子关系的两个类中,类中相同名字的属性会使得从父类中继承过来的同名属性变得不可见,

重载和重写的区别

  • 重写(Override):重写是子类对父类的允许访问的方法进行重新编写,返回值和形参都不能改变,子类根据需要实现自己的方法
  • 重载(Overload):是在一个类里面,方法名相同,而参数不同(个数和类型不同),返回类型可以相同也可以不同。

方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性的表现。
重写发生在子类和父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好的访问,不能比父类被重写方法声明更多的异常。—运行时多态
重写原则:
参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写的方法的返回类型一致
构造方法不能被重写,声明为final的方法不能被重写,声明为static的方法不能被重写,但是能够被再次申明
访问权限不能比父类中被重写的方法的访问权限更低。
重写的父类方法能够抛出任何非强制异常(也叫非运行异常),无论被重写的方法是否抛出异常,但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明更广泛的强制性异常,反之则可以。

Java是否可以继承String类:

  • String是final类型的,所以不能被继承

Java类支持多继承吗?可以实现多个接口吗?

java不支持多继承,但是类可以实现多个接口,间接的实现多继承,也可以通过内部类。

接口和抽象的区别:

  • 接口和抽象类都是继承树的上层,他们的共同点如下:
    1) 都是上层的抽象层
    2) 都不能够被实例化
    3) 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能
  • 两者的区别:
    1) 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写,提高代码复用性–抽象类的优势,而接口当中只能有抽象的方法(jdk8之后也可以有实现方法)
    2) 一个类只能继承一个直接父类,这个父类可以是具体的类也可以是抽象类,但是一个类可以实现多个接口,接口的设计具有更大的扩展性,而抽象类的设计必须十分谨慎。
    3) 抽象级别:接口 大于 抽象类 大于 实现类
    4) 接口的设计目的:是针对类的行为进行约束,侧重于动作,而抽象类的设计目的是代码复用。
    5) 抽象类是 is a的关系,接口是has a的关系。

Java中的修饰符的作用域以及可见性

  • public :当前类、子类,同一包、其他包都可以访问
  • protected:当前类,子类以及同一包可以访问,其它包不可以
  • default:当前类和同一包可以访问,子类和其他包不可以
  • private:当前类可以访问,同一包、子类、其它包都不可以访问
    控制的范围是逐级减少的,public > protected > default > private

Java中== 与equals的区别:

  • ==比较的是变量(栈)内存中存放的对象(堆)的内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
  • equals来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
    如果想自己定义什么是相等,可以重写equals方法。用于判断是否是自己定义的相等

静态变量和实例变量的区别?

  • 一个static方法内不可以调用非static方法
    因为非静态方法是与对象关联起来的,必须创建一个对象才可以进行方法的调用,而非静态的方法是不需要创建对象的,所以两者之间相互矛盾,不能在静态方法使用调用非静态的方法。
  • static方法是静态方法,是属于类的方法;非static方法是属于对象的方法,所以要想在static方法中调用非static方法要先创建一个对象,再由这个对象来调用。
    本质是JVM加载顺序决定的,加载static方法的时候非静态方法还没有初始化,当然不能调用了

Integer和int的类型

Integer是int的封装类,从java5以后引入了自动装箱和拆箱机制,两个可以互相转换,int是基本的数据类型不能是null,但是Integer可以是null。为空值。
Integer是对象,用一个引用指向这个对象,而int是基本类型,直接存储数据。
Integer提供了好多与整数相关的操作方法,例如:将一个字符串转换成整数等.

Integer的缓存问题


public class IntTest {

public static void main(String[] args) {
Integer a = 100, b = 100, c = 150, d = 150;
System.out.println(a == b);
System.out.println(c == d);
}

请问输出结果如何?
答案是:第一个是true,第二个是false
为什么会这样呢? 是因为Integer缓存了-128到127之间的整数对象,如果是-128到127之间的整数,则会使用整数缓存对象,否则就new一个整形对象。所以a和b是缓存好的对象,但是C和d是new出来的,所以在比较的时候就会出现现在的结果啦

String、StringBuilder、StringBuffer的优缺点

  • String是字符串常量,StringBuilder和StringBuffer都是字符串变量,

Java字符串驻留池

Java – 字符串常量池介绍

String a = "aa";
Stirng b = "aa";
System.out.priont(a == b);

输出结果为trye,因为==比较的引用地址,String被放在了常量池之中,第一次出现”aa”的时候,JVM吧他放在了常量池里面,当第二次出现”aa”的时候,JVM就会把引用引到之前的地方,所以这两比较的时候会出现true

String a = new String("aa");
String b = new String("aa");
System.out.print(a==b);

这里输出结果是false,因为new会强制的分配空间。

String str1 = "java";
String str2 = "blog";
String s = str1+str2;
System.out.print(s=="javablog");

这里是false,JVM确实会对型如String str1 = “java”; 的String对象放在字符串常量池里,但是它是在编译时刻那么做的,而String s = str1+str2; 是在运行时刻才能知道(我们当然一眼就看穿了,可是Java必须在运行时才知道的,人脑和电脑的结构不同),也就是说str1+str2是在堆里创建的, s引用当然不可能指向字符串常量池里的对象。

String str1 = "java";
String str2 = new String("java");
System.out.print(str1.equals(str2));

是true,无论在常量池还是堆中的对象,用equals()方法比较的就是内容


public class StringCompare {
public static void A() {
String str1 = "java";
String str2 = "java";
System.out.println(str1 == str2); //true
}
public static void B() {
String str1 = new String("java");
String str2 = new String("java");
System.out.println(str1 == str2); //false
}
public static void C() {
String str1 = "java";
String str2 = "blog";
String s = str1 + str2;
System.out.println(s == "javablog"); //false
}
public static void C2() {
String str1 = "javablog";
String str2 = "java"+"blog"; //在编译时被优化成String str2 = "javablog";
System.out.println(str1 == str2); //true
}
public static void D() {
String s1 = "java";
String s2 = new String("java");
System.out.println(s1.intern() == s2.intern()); //true
}

public static void E() {
String str1 = "java";
String str2 = new String("java");
System.out.println(str1.equals(str2)); //true
}

public static void main(String[] args){
A();
B();
C();
C2();
D();
E();
}
}

输出============

true
false
false
true
true
true

线程安全性

StringBuilder是线程不安全的,而StringBuffer是线程安全的(StringBuffer中很多方法带有synchronized关键字)

总结

String:适用于少量字符串操作的情况;
StringBuilder:适用于在单线程下在字符缓冲区进行大量操作的情况;
StringBuffer:适用于在多线程下在字符缓冲区进行大量操作的情况

java程序初始化的顺序是什么样子的?

一般遵循三个原则:

  • 1.静态变量优先于非静态变量初始化,其中静态变量只初始化一次,而非静态变量可能会初始化很多次
  • 2.父类优先子类进行初始化
  • 3.按照成员变量定义顺序进行初始化,即使变量定义散布于方法之中,它们依然在方法调用之前(包括构造函数)先初始化。
    父类静态字段初始化
    父类静态代码块
    子类静态字段初始化
    子类静态代码块
    父类普通字段初始化
    父类构造代码块({//代码}) –优先于构造函数执行
    父类构造函数
    子类普通字段初始化
    子类构造代码块
    子类构造函数

Java中的反射:

转看另一篇专门讲反射机制 Java中的反射机制

Java中的Try catch finally的问题

  • 1.不管有没有异常,finally中的代码都会执行
  • 2.当try、catch中有return语句时,finally中的代码依然会继续执行
  • 3.finally是在return后面的表达式运算之后执行的,此时并没有返回运算后的值,而是把值保存起来,不管finally对该值做了任何改变,返回的值都不会改变,依然返回保存起来的值,也就是说方法的返回值是在finally运算之前就确定了的。
  • 4.如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try 中return返回的就是finally中改变后的属性值
  • 5.finally代码最好不要包含return,程序会提前退出,也就是说返回的值不是try catch中的值
    先执行try中的语句,包括return后面的表达式
    有异常时,先执行catch中的语句,包括return 后面的表达式;
    然后执行fianlly中的语句,如果finally里面有return语句,会提前退出
    最后执行try 中的return,有异常执行catch中return;

Java中的位移操作

  • <<表示左移移,不分正负数,低位补0;
    //正数: r = 20 << 2
    //20的二进制补码:0001 0100 正数的补码就是他本身
    //向左移动两位后:0101 0000
    //结果:r = 80
    //r = -20 << 2
    //-20 的二进制原码 :1001 0100
    //-20 的二进制反码 :1110 1011
    //-20 的二进制补码 :1110 1100 为什么会有补码,是因为方便进行数字的计算
    // 移两位后的补码:1011 0000
    // 反码:1010 1111
    // 原码:1101 0000
    // 结果:r = -80
  • \>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
    //正数:r = 20 >> 2

    //  20的二进制补码:0001 0100

    //  向右移动两位后:0000 0101

    //       结果:r = 5

    //负数:r = -20 >> 2

    //  -20 的二进制原码 :1001 0100

    //  -20 的二进制反码 :1110 1011

    //  -20 的二进制补码 :1110 1100

    //  右移两位后的补码:1111 1011

    //        反码:1111 1010

    //        原码:1000 0101

    //        结果:r = -5
  • \>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0;
    //正数: r = 20 >>> 2

    //    的结果与 r = 20 >> 2 相同;

    //负数: r = -20 >>> 2

    //注:以下数据类型默认为int 32位

    //  -20:源码:10000000 00000000 00000000 00010100

    //    反码:11111111 11111111 11111111 11101011

    //    补码:11111111 11111111 11111111 11101100

    //    右移:00111111 11111111 11111111 11111011

    //    结果:r = 1073741819

HashMap扩容机制

什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值—即当前数组的长度乘以加载因子的值的时候,就要自动扩容,
扩容一般是成倍扩容。

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/03/04/cl35o0mof0010p4tg3snj5mop/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论