Java基础

1.面向对象四大特性

封装、继承、多态、抽象

1.1封装

封装将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

访问修饰符:
访问修饰符
类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。

1.2 继承

子类继承父类获得父类的属性和方法。通过 extends 关键字实现继承。java 不支持多继承但是支持多重继承。
特性:子类拥有父类非 private 的属性、方法。子类可以对父类进行扩展,拥有自己的属性和方法。子类可以用自己的方式实现父类的方法。
缺点:提高了类之间的耦合度。

super关键字:通过super关键字来实现对父类成员的访问。
this关键字:指向自己的引用。
final 关键字:声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。
实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final
构造器:子类是不继承父类的构造器的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。

1.3多态(重载、重写)

一般多态指运行时多态。多态指允许不同类的对象对同一消息做出响应,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

两种实现方式:方法的重载(overload)重载指一个类中有多个同名的方法,但这些方法有着不同的参数(个数或者类型),方法返回值和访问修饰符可以不同,因此编译时可以确定到底调用哪个方法,是一种编译时多态。

方法的覆盖(override,也叫重写)发生在父子类中,子类重写父类的方法;其中方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。是运行时多态。

方法重写特点:1.子类重写父类方法2.重写方法的访问修饰符大于等于被重写方法的访问修饰符。3.参数列表相同,返回值一致。4.被重写的方法不能被private5.静态方法不能被重写为非静态方法(编译出错)
多态三要素:1.要有继承关系2.子类要重写父类的方法3.父类引用指向子类对象(向上转型)
注:实现接口也被当做继承
运行时多态的实现方式为:继承和接口,继承是通过重写父类的同一方法的几个不同子类来体现的,那么接口就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。

1.4 抽象

抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。抽象包括 过程抽象和数据抽象。
例如苹果、香蕉、生梨、葡萄、桃子等,它们共同的特性就是水果。得出水果概念的过程,就是一个抽象的过程。

访问修饰符

public 表示类成员可以在任何类中访问。
private 表示类成员只能从自身所在的类中访问。
protected 的可见性在 public 和默认之间,表示类成员可以在同一个包里的任何类中访问,也可以在该类的子类中访问。
如果不加任何访问修饰符,则称为默认修饰符,表示类成员可以在同一个包里的任何类中访问,此时也称为包私有或包内访问。
在这里插入图片描述

2.java 数据类型

  • 基本数据类型

    • 数值型
      • 整数类型(byte,short,int,long)
      • 浮点类型(float,double)
    • 字符型(char)
    • 布尔型(boolean)
  • 引用数据类型

    • 类(class)
    • 接口(interface)
    • 数组([])
    • String
      在这里插入图片描述

    3.包装类

    在这里插入图片描述

包装类的构造方法

可以通过包装类的构造方法创建包装对象。调用构造方法时,构造方法的参数值可以是基本数据类型的值,也可以是表示值的字符串。包装类的构造方法都是有参数的,没有无参数构造方法。包装类的实例都是不可变的,一旦创建了包装对象,其内部的值就不能再改变。

自动装箱和自动拆箱

从 JDK 1.5 开始,基本数据类型和包装类之间可以进行自动转换。
将基本数据类型的值转换为包装对象,称为装箱。将包装对象转换为基本数据类型的值,称为拆箱。

3.final 有什么用

用于修饰类、属性和方法;

  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
    (1)修饰成员变量
    如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
    如果final修饰的是实例变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。
    (2)修饰局部变量
    系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,
    即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码
    中对final变量赋初值(仅一次)
    (3)修饰基本类型数据和引用类型数据
    如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
    如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变
    的。

4.构造器(constructor)是否可被重写(override)

答:构造器不能被继承,因此不能被重写,但可以被重载。

5.this关键字的用法

this的用法在java中大体可以分为3种:
1.普通的直接引用,this相当于是指向当前对象本身。
2.形参与成员名字重名,用this来区分:
3.引用本类的构造函数

6.super关键字的用法

super也有三种用法:
1.普通的直接引用,与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。
2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
3、引用父类构造函数

当子类构造函数需要显式调用父类构造函数,super()必须为构造函数中的第一条语句。

7.this与super的区别

  • super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
  • this和super不能同时出现在一个构造函数里面。
  • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。

8.static

static用途

  • static用于创建独立于具体对象的静态变量或者静态方法。即使没有创建对象,也能使用属性和调用方法 !

  • 用来形成静态代码块以优化程序性能 。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

  • 很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行,可以用来优化程序性能。

  • 局部变量不能被声明为 static 变量。静态方法不能使用类的非静态变量。

静态类 特点

一、静态类的特点
1.全局唯一,任何一次的修改都是全局性的影响
2.只加载一次,优先于非静态
3.使用方式上不依赖于实例对象。
4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。

静态类和非静态类之间的区别

内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用
非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员
一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面,其实就是静态类不用先创建外部类。可以静态类看做外部类的静态变量,使用就不要外部类实例;而非静态就必须先实例化。

静态方法与非静态方法

静态方法只能访问静态成员, 非静态方法既可以访问静态又可以访问非静态;静态方法中不可以定义this,super关键字;因为this代表是对象,而静态存在时,有可能没有对象,且静态优先于对象存在。所以静态方法运行时,this是没有任何对象代表的。

静态成员与非静态成员

静态变量也称为类变量,也就是直接可以被类名调用的变量,这个变量是所属于类的;

非静态变量称为成员变量,或者实例变量,是被对象调用的,是所属具体对象的。

静态变量随着类的加载而加载,也意味着随着类的消失而消失,生命周期最长;

实例变量,随着对象的创建而加载,随着对象的消失而消失,按照对象的生命周期而存在。

静态变量存储在方法区的静态区中;

实例变量存在于对象所属的堆内存中。

静态变量数据,被所有对象所共享;

实例变量是对象中的特有数据。

static 与final结合

在这里插入图片描述

初始化块

代码初始化块属于类成员,在加载类时或创建对象时会隐式调用代码初始块。使用初始化块的好处是可以减少多个构造器内的重复代码。

初始化块的分类
初始化块可以分成静态初始化块和非静态初始化块,前者在加载类时被隐式调用,后者在创建对象时被隐式调用。

单个类的初始化块的执行顺序
如果有初始化块,则初始化块会在其他代码之前被执行。具体而言,静态初始化块会在静态方法之前被执行,非静态初始化块会在构造器和实例方法之前被执行。

由于静态初始化块在加载类时被调用,因此静态初始化块会最先执行,且只会执行一次。

由于非静态初始化块在创建对象时被调用,因此每次创建对象时都会执行非静态初始化块以及执行构造器。非静态初始化块的执行在静态初始化块的执行之后、构造器的执行之前。

存在继承关系的初始化块的执行顺序
如果存在继承关系,则在对子类进行类的加载和创建对象时,也会对父类进行类的加载和创建对象。执行顺序仍然是静态初始化块、非静态初始化块、构造器,由于存在继承关系,因此情况较为复杂。

对于两个类的情况,即一个父类和一个子类,执行顺序如下。

  • 执行父类的静态初始化块。

  • 执行子类的静态初始化块。

  • 执行父类的非静态初始化块。

  • 执行父类的构造器。

  • 执行子类的非静态初始化块。

  • 执行子类的构造器。

更一般的情况,对于多个类之间的继承关系(可能超过两个类,例如 B 继承了 A,C 继承了 B),执行顺序如下。

按照从父类到子类的顺序,依次执行每个类的静态初始化块。

按照从父类到子类的顺序,对于每个类,依次执行非静态初始化块和构造器,然后执行子类的非静态初始化块和构造器,直到所有类执行完毕。

9.break ,continue ,return 的区别及作用

break 结束当前的循环体
continue 跳出本次循环,继续执行下次循环
return 结束当前的方法,不再执行后边程序, 直接返回

10.定义一个不做事且没有参数的构造方法的作用

在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中无参构造。如果此时父类没有无参构造,则编译时将发生错误。解决办法是在父类里加上一个无参构造。

11.错误和异常

Error 和 Exception 都继承Throwable类,

Error 表示的是程序无法处理的错误,是运行应用程序中比较严重的错误,大多数与代码编写者执行的操作无关,是JVM出现的问题,例如虚拟机运行错误,类定义错误,或者OutOfMemoryError(资源耗尽错误)。当发生错误的时候,JVM一般会选择线程终止。发生了错误,本质上不应该试图去处理它所引起的异常

Exception 是程序本身可以处理的异常。其中的Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常 必须在编写代码时,使用try catch捕获(比如:IOException异常)。非检查性异常 在代码编写使,可以忽略捕获操作(比如ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的。

异常处理的操作

Java 的异常处理基于三种操作:声明异常、抛出异常和捕获异常。

声明异常
如果一个方法可能抛出异常,则需要在方法声明中使用关键字 throws 声明异常。如果一个方法可能抛出多种类型的异常,则需要在关键字 throws 之后依次列举可能抛出的异常类型。

抛出异常
如果程序检查到错误,则可以创建一个异常的实例并抛出该异常实例。使用关键字 throw 抛出异常。

需要注意声明异常的关键字 throws 和抛出异常的关键字 throw 的区别。

捕获异常
捕获异常通过 try-catch 块实现。每个 catch 块包含一个特定异常类型的参数,如果需要捕获多种异常,则需要使用多个 catch 块,每个 catch 块分别包含一个特定异常类型的参数。

如果 try 块的执行过程中没有出现异常,则跳过 catch 块。

如果 try 块中的一个语句抛出一个异常,则跳过 try 块中剩下的语句,寻找可以处理该异常的代码,处理异常的代码称为异常处理器。具体而言,依次检查每个 catch 块,寻找可以处理该异常的 catch 块。

  • 如果发现一个 catch 块的参数的异常类型和抛出的异常实例匹配,则将异常实例赋给该 catch 块的参数,执行该 catch 块的语句。

  • 如果在当前方法中没有发现异常处理器,则异常没有被捕获和处理,退出当前的方法,并将异常传递给当前方法的调用者,继续寻找异常处理器。

如果一个 catch 块可以捕获一个父类的异常对象,则该 catch 块也能捕获该父类的所有子类的异常对象。

由于父类包含子类,因此需要注意 catch 块的顺序,子类异常对应的 catch 块必须出现在父类异常的 catch 块之前,否则会出现编译错误。

列举五种 Java 常见的异常类型

NullPointerException:空指针异常。
ArrayIndexOutOfBoundsException:数组下标越界异常。
ArithmeticException:算术运算异常。
NumberFormatException:数字格式异常。
IOException:输入输出异常。

13.静态代理和动态代理的区别

静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一单需要修改接口,代理类和委托类都需要修改。

14.JDK动态代理和CGLIB动态代理的区别

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final。

15.java中的clone方法

java在处理基本数据类型(int、char、double)时,都是按值传递,其他类型都是按引用传递。在使用 = 对对象赋值时也是按引用传递。在实际编程中,需要创建一个和已有对象相同且独立的对象,仅靠赋值操作是无法达到目的。需要用clone()方法。Object类提供clone()方法,返回一个对象的复制,新对象,不是引用。

16.深克隆和浅克隆?

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

17.clone()与new的区别

clone()不会调用构造方法;new会调用构造方法
clone()更快。clone()能快速创建一个已有对象的副本,即创建对象并且将已有对象中所有属性值克隆;new只能在JVM中申请一个空的内存区域,对象的属性值要通过构造方法赋值

18.抽象类和接口

  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类只能继承一个,接口可以实现多个。

接口的设计目的,是对类的行为进行约束,也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。

而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。

抽象类是对类本质的抽象,表达的是 is a 的关系。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。

而接口是对行为的抽象,表达的是 like a 的关系 。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。

使用场景:当关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为java的每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度。

19.内部类

将一个类定义在另一个给类里面或者方法里面,这样的类就被称为内部类。内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类

  • 成员内部类可以无条件访问外部类的属性和方法,但是外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法
  • 局部内部类存在于方法中。他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
  • 通过实现接口创建一个匿名类对象或者通过继承类来创建一个匿名内部类对象。匿名内部类没有构造方法。匿名内部类和局部内部类只能访问外部类的final变量。
  • 静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。同时静态内部类也有它的特殊性。因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。同时可以知道成员内部类里面是不能含静态属性或方法的。
    内部类的好处
    完善了Java多继承机制,由于每一个内部类都可以独立的继承接口或类,所以无论外部类是否继承或实现了某个类或接口,对于内部类没有影响。
    方便写事件驱动程序。

20.final、 finally 、finalize的区别

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

21.++i与i++区别

1.当表达式不参与其他运算时:i++与++i最终都是实现了对i的加1,没有区别。
2.当i++与++i参与赋值运算时(如上的i=i++或i=++i):赋值对象的最终值均为操作数栈顶的值 记为b。而 b的值i++是加前的值,++i是加后的值。
3.当i++和++i参与算数运算时:i++是把操作数i的值作为值参与运算,然后i再加1;++i是自己先加1,然后作为值参与运算。
4. ++i的效率要更块一些
理解:

  • 在使用i=i++的过程中,它会先把i的原始值0复制到操作数栈中,然后再对局部变量表中的0进行+1操作使得i变为了1,此时操作数栈顶的值为0,然后执行赋值操作时候使用的是弹出的操作数栈顶的值,所以最后i又被修改为了0;
  • 而i=++i的过程则是先对局部变量表中i的原始值进行加1的操作,即使得i由0变为1,然后将i的值复制到操作数栈,最后赋值即弹出操作数栈顶的值。
  • i++;和++i;的执行过程和结果是一样的。
  • 在使用i++和++i赋值的过程中,他们区别在于前者先复制当前数据,再进行原始值加1的操作,后者则先进行了原始值加1的操作,再对计算后的结果进行了复制,最后返回的其实都是放入操作数栈的拷贝。

22.字符串创建与存储机制

String str1= “abc”; 在编译期,JVM会去常量池来查找是否存在“abc”,如果不存在,就在常量池中开辟一个空间来存储“abc”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。

String str2 = new String(“abc”) ;在编译阶段JVM先去常量池中查找是否存在“abc”,如果过不存在,则在常量池中开辟一个空间存储“abc”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“abc”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new

出来的这个String对象的地址值。

也就是说,前者在初始化的时候可能创建了一个对象,也可能一个对象也没有创建;后者因为new关键字,至少在内存中创建了一个对象,也有可能是两个对象

在这里插入图片描述
在这里插入图片描述

23. equals和==

  • equals:在object类中equals只能处理引用类型,比较的是对象的首地址。在String 类等一些包装类中,对Object类的equals 方法进行重写,首先比较首地址,其次比较每一个字符。
  • ==:1.基本数据类型:根据基本数据类型的值判断是否。相等返回true,反之返回false
    2.引用数据类型:比较引用类型变量的首地址值是否相等。

24.String、StringBuffer、StringBuilder

String是final修饰的,是不可变类,String对象一旦被创建,其值不能被改变。如果用String保存经常被修改的字符串时,会生成很多无用的对象,这些对象会被垃圾回收器回收,因此会影响程序的性能。当一个字符串经常被修改时,最好用StringBuffer。SringBuilder与StringBuffer类似,都是字符串缓冲区,但是StringBuilder不是线程安全的,单线程中StringBuilder效率会高一些,StringBuffer方法都是synchronized修饰的,多线程用线程安全的StringBuffer,StringBuffer在多个实例上的串行操作顺序与每个线程方法调用顺序一致。
**性能:**StringBuilder > StringBuffer > String
**场景:**经常需要改变字符串内容时,优先使用StringBuilder,多线程使用共享变量时使用StringBuffer

25.finally块什么时候被执行

1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;但如果是引用类型,修改的属性会以finally修改后的为准;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
举例:
情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。

情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。

情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.

情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。

情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。

情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。

26.IO流的实现机制

根据处理数据类型的不同,流可以分为两大类,字节流和字符流,字节流,以字节为单位,包含两个抽象的类inputStream输入流和outputStream输出流,字符流以字符为单位,它包含两个抽象类,Reader入流和Writer输出流,字节流和字符流的主要区别为字节流在输入输出时不会用到缓存,而字符流用到了缓存。java IO 类在设计时采用的装饰者模式。

27.Java Socker

在这里插入图片描述

28.Object 类的方法

toString
该方法返回一个代表该对象的字符串。该方法的默认实现返回的字符串在绝大多数情况下是没有信息量的,因此通常都需要在子类中重写该方法。

equals
该方法检验两个对象是否相等。该方法的默认实现使用 == 运算符检验两个对象是否相等,通常都需要在子类中重写该方法。

hashCode
public native int hashCode()
该方法返回对象的散列码。关键字 native 表示实现方法的编程语言不是 Java。
散列码是一个整数,用于在散列集合中存储并能快速查找对象。根据散列约定,如果两个对象相同,它们的散列码一定相同,因此如果在子类中重写了 equals 方法,必须在该子类中重写 hashCode 方法,以保证两个相等的对象对应的散列码是相同的。

两个相等的对象一定具有相同的散列码,两个不同的对象也可能具有相同的散列码。实现 hashCode 方法时,应避免过多地出现两个不同的对象也可能具有相同的散列码的情况。

finalize
protected void finalize() throws Throwable
该方法用于垃圾回收。如果一个对象不再能被访问,就变成了垃圾,finalize 方法会被该对象的垃圾回收程序调用。该方法的默认实现不做任何事,如果必要,子类应该重写该方法。

该方法可能抛出 Throwable 异常。

clone
protected native Object clone() throws CloneNotSupportedException
该方法用于复制一个对象,创建一个有单独内存空间的新对象。

不是所有的对象都可以复制,只有当一个类实现了 java.lang.Cloneable 接口时,这个类的对象才能被复制。

该方法可能抛出 CloneNotSupportedException 异常。

getClass
public final native Class<?> getClass()
该方法返回对象的元对象。元对象是一个包含类信息的对象,包括类名、构造方法和方法等。

一个类只有一个元对象。每个对象都有一个元对象,同一个类的多个对象对应的元对象相同。

29.java泛型

泛型,即“参数化类型”。就是将类型也定义成参数形式,然后在使用/调用时传入具体的类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

30.什么是Lambda表达式

Lambda Expression可以定义为允许用户将方法作为参数传递的匿名函数。这有助于删除大量的样板代码。Lambda函数没有访问修饰符(私有,公共或受保护),没有返回类型声明和没有名称。

Lambda表达式允许用户将“函数”传递给代码。所以,与以前需要一整套的接口/抽象类想必,我们可以更容易地编写代码。例如,假设我们的代码具有一些复杂的循环/条件逻辑或工作流程。使用lambda表达式,在那些有难度的地方,可以得到很好的解决。

31.Lambda函数的优点

直到Java 8列表和集合通常由客户端代码从集合中获取迭代器来处理,然后使用它迭代其元素并依次处理每个元素。如果要并行处理不同的元素,那么客户代码而不是集合的责任就是组织它。 通过Java 8,可以更轻松地在多个线程上分发集合的处理。 集合现在可以在内部组织自己的迭代,将并行化的责任从客户端代码转移到库代码中。

更少的代码行。如上所述,用户必须仅以声明方式声明要执行的操作。 n > System.out.println(“Hello World”+ n); 所以用户必须键入减少的代码量。

使用Java 8 Lambda表达式可以实现更高的效率。通过使用具有多核的CPU,用户可以通过使用lambda并行处理集合来利用多核CPU。

32.反射机制

反射机制允许程序在运行时取得已知对象所属的类,获取这个类的所有成员变量和方法,在运行时创建对象,在运行时改对象的变成员变量或者调用方法。由此便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现;JDBC原生代码注册驱动;hibernate 的实体类;Spring的AOP 等等。但是凡事都有两面性,反射使用不当会造成很高的资源消耗。

3种获取class类的方法

在这里插入图片描述

new对象和反射得到对象的区别

  • 在使用反射的时候,必须确保这个类已经加载并已经连接了。使用 new 的时候,这个类可以没有被加- 载,也可以已经被加载。
  • new 关键字可以调用任何 public 构造方法。反射可以调用特定构造方法,甚至私有方法。
  • new 关键字是强类型的,效率相对较高。 反射是弱类型的,效率低。
  • 反射提供了一种更加灵活的方式创建对象,得到对象的信息。如 Spring 中 AOP 等的使用,动态代理的使用,都是基于反射的。解耦。

Class 类

Class 类的作用是在程序运行时保存每个对象所属的类的信息,在程序运行时分析类。一个 Class 类型的对象表示一个特定类的属性。

有三种方法可以得到 Class 类型的实例。

第一种方法是对一个对象调用 getClass 方法,获得该对象所属的类的 Class 对象。

第二种方法是调用静态方法 Class.forName,将类名作为参数,获得类名对应的 Class 对象。

第三种方法是对任意的 Java 类型 T(包括基本数据类型、引用类型、数组、关键字 void),调用 T.class 获得类型 T 对应的 Class 对象,此时获得的 Class 对象表示一个类型,但是这个类型不一定是一种类。

三种方法中,通过静态方法 Class.forName 获得 Class 对象是最常用的。

Class 类的常用方法
Class 类中最常用的方法是 getName,该方法返回类的名字。

Class 类中的 getFields、getMethods 和 getConstructors 方法分别返回类中所有的公有(即使用可见修饰符 public 修饰)的数据域、方法和构造方法。

Class 类中的 getDeclaredFields、getDeclaredMethods 和 getDeclaredConstructors 方法分别返回类中所有的数据域、方法和构造方法(包括所有可见修饰符)。

Class 类中的 getField、getMethod 和 getConstructor 方法分别返回类中单个的公有(即使用可见修饰符 public 修饰)的数据域、方法和构造方法。

Class 类中的 getDeclaredField、getDeclaredMethod 和 getDeclaredConstructor 方法分别返回类中单个的数据域、方法和构造方法(包括所有可见修饰符)。

33 序列化和反序列化

把对象转换成字节序列的过程称为对象的序列化,把字节序列恢复成对象的过程称为对象的反序列化。

可序列化接口 Serializable
只有当一个类实现了 Serializable 接口时,这个类的实例才是可序列化的。Serializable 接口是一个标识接口,用于标识一个对象是否可被序列化,该接口不包含任何数据域和方法。

如果试图对一个没有实现 Serializable 接口的类的实例进行序列化,会抛出 NotSerializableException 异常。

将一个对象序列化时,会将该对象的数据域进行序列化,不会对静态数据域进行序列化。

关键字 transient
如果一个对象的类实现了 Serializable 接口,但是包含一个不可序列化的数据域,则该对象不可序列化。为了使该对象可序列化,需要给不可序列化的数据域加上关键字 transient。

如果一个数据域可序列化,但是不想将这个数据域序列化,也可以给该数据域加上关键字 transient。

在序列化的过程中,加了关键字 transient 的数据域将被忽略。

34 自定义比较方法

有两个接口可以实现对象之间的排序和比较大小。

Comparable 接口是排序接口。如果一个类实现了 Comparable 接口,则该类的对象可以排序。Comparable 接口包含一个抽象方法 compareTo,实现 Comparable 接口的类需要实现该方法,定义排序的依据。

Comparator 接口是比较器接口。如果一个类本身不支持排序(即没有实现 Comparable 接口),但是又需要对该类的对象排序,则可以通过实现 Comparator 接口的方式建立比较器。Comparator 接口包含两个抽象方法 compare 和 equals,其中 compare 方法是必须在实现类中实现的,而 equals 方法在任何类中默认已经实现。

如果需要对一个数组或列表中的多个对象进行排序,则可以将对象的类定义成实现 Comparable 接口,也可以在排序时定义 Comparator 比较器。

35 ThreadLocal

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

内部结构

(1)每个Thread维护着一个ThreadLocalMap的引用

(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。

(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中

(5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。

(6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

使用场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离

3、进行事务操作,用于存储线程事务信息。

4、数据库连接,Session会话管理。

内存泄漏问题

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、当ThreadLocal是null了,也就是要被垃圾回收器回收,但是ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

36. 类方法 和实例方法区别

  • 类方法,也称静态方法,指的是用static关键字修饰的方法。此方法属类本身的方法,不属于类的某一个实例(对象)。类方法中不可直接使用实例变量。其调用方式有三种:可直接调用、类名.方法名、对象名.方法名。
  • 实例方法指的是不用static关键字修饰的方法。每个实例对象都有自身的实例方法,互相独立,不共享一个。其调用方式只能是对象名+方法名。
  • 静态方法在程序开始时生成内存,实例方法在程序运行中生成内存,所以静态方法可以直接调用。静态方法常驻内存,实例方法不是,所以类方法效率高但占内存。

一. 实例方法

当类的字节码文件加载到内存中时,类的实例方法并没有被分配入口地址,只有当该类的对象创建以后,实例方法才分配了入口地址。从而实例方法可以被类创建的所有对象调用,还有一点需要注意,当我们创建第一个类的对象时,实例方法的入口地址会完成分配,当后续在创建对象时,不会再分配新的入口地址,也可以说,该类的所有对象共享实例方法的入口地址,当该类的所有对象被销毁,入口地址才会消失。

二. 类方法

当类的字节码文件加载到内存,类方法的入口地址就会分配完成,所以类方法不仅可以被该类的对象调用,也可以直接通过类名完成调用。类方法的入口地址只有程序退出时消失。

因为类方法的入口地址的分配要早于实例方法的入口地址分配时间,所有在定义类方法和实例方法是有以下规则需要遵循:

  1. 在类方法中不能引用实例变量

实例变量的定义类似实例方法,没有用static修饰的变量,实例变量的创建与实例方法的创建相同,也是在类的对象创建时完成,所以在类方法中是不能引用实例变量的,因为这个时候实例变量还没有分配内存地址。

  1. 在类方法中不能使用super和this关键字

这是因为super和this都指向的是父类和本类的对象,而在类方法中调用的时候,这些指代的对象有可能都还没有创建。

  1. 类方法中不能直接调用实例方法
    静态方法不能直接调用实例方法和变量,但可以间接调用(即在静态方法中创建类的实例,然后调用)

原因同1。

与类方法相比,实例方法的定义就没有什么限制了:

  1. 实例方法可以引用类变量和实例变量

  2. 实例方法可以使用super和this关键字

  3. 实例方法中可以调用类方法

37OOM内存溢出的原因和处理方法

1.当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 java.lang.OutOfMemoryError:Javaheap space 错误
原因分析
Javaheap space 错误产生的常见原因可以分为以下几类:
1、请求创建一个超大对象,通常是一个大数组。
2、超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
3、过度使用终结器(Finalizer),该对象没有立即被 GC。
4、内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。

解决方案
针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:
1、如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。
2、如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
3、如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。

2.、Out of swap space?
该错误表示所有可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报 Outof swap space? 错误。

原因分析
该错误出现的常见原因包括以下几类:
1、地址空间不足;
2、物理内存已耗光;
3、应用程序的本地内存泄漏(native leak),例如不断申请本地内存,却不释放。
4、执行 jmap-histo:live 命令,强制执行 Full GC;如果几次执行后内存明显下降,则基本确认为 Direct ByteBuffer 问题。

解决方案
根据错误原因可以采取如下解决方案:
1、升级地址空间为 64 bit;
2、使用 Arthas 检查是否为 Inflater/Deflater 解压缩问题,如果是,则显式调用 end 方法。
3、Direct ByteBuffer 问题可以通过启动参数 -XX:MaxDirectMemorySize 调低阈值。
4、升级服务器配置/隔离部署,避免争用。

3.、Direct buffer memory
Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory Mapped File)实现高速 IO。

原因分析
Direct ByteBuffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 Directbuffer memory 错误。

解决方案
1、Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通过 Arthas 等在线诊断工具拦截该方法进行排查。
2、检查是否直接或间接使用了 NIO,如 netty,jetty 等。
3、通过启动参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值。
4、检查 JVM 参数是否有 -XX:+DisableExplicitGC 选项,如果有就去掉,因为该参数会使 System.gc() 失效。
5、检查堆外内存使用代码,确认是否存在内存泄漏;或者通过反射调用 sun.misc.Cleaner 的 clean() 方法来主动释放被 Direct ByteBuffer 持有的内存空间。
6、内存容量确实不足,升级配置。

参考:
java中常用的数据结构–Collection接口及其子类
如何解决ArrayList线程不安全
java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值