Java知识点

一、Java概述

1、何为编程

编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。

为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。

2、什么是Java

Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。

3、Java SE

Java SE:可以理解为Java的基石,如果java是一座高楼,se就是他的地基。

官方解释:

Java SE(Java Platform,Standard Edition)标准版 Java SE 以前称为 J2SE。 它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。 Java SE 包含了支持 Java Web服务开发的类,并为 Java Platform,Enterprise Edition(Java EE)提供基础。

javaSE包含了什么:面向对象,多线程,IO流,javaSwing等。

4、Java EE

Java EE:在SE的基础上进行了一些规范,可以理解为一种框架。

官方解释:

Java EE,Java 平台企业版(Java Platform Enterprise Edition) 之前称为Java 2 Platform, Enterprise Edition (J2EE) 2018年3月更名为 Jakarta EE(这个名称应该还没有得到群众认可)。 是 Sun 公司为企业级应用推出的标准平台,用来开发B/S架构软件。

java EE包含了什么:serclet、jstl、jsp、spring、mybatis等

5、java ME

Java ME 又称为J2ME(Java Platform,Micro Edition) 是为机顶盒、移动电话和PDA之类嵌入式消费电子设备提供的Java语言平台,包括虚拟机和一系列标准化的Java API。

6、JVM、JRE和JDK

1.JVM

Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

2.JRE

Java Runtime Environment:Java 运行时环境(简称 JRE),是一个软件层,它运行在计算机的操作系统软件之上,并提供特定 Java 程序运行所需的类库和其他资源。JRE的内部有一个Java虚拟机,以及一些标准的函数库,核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包。

如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可

3.JDK

Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

7、跨平台的原理

所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。

实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。

8、Java语言有哪些特点

简单易学(Java语言的语法与C语言和C++语言很接近)

面向对象(封装,继承,多态)

平台无关性(Java虚拟机实现平台无关性)

支持网络编程并且很方便

支持多线程(多线程机制使应用程序在同一时间并行执行多项任)

健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)

安全性

9、字节码以及好处

字节码:

Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

采用字节码的好处:

Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

先看下java中的编译器和解释器:

Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。

Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。

10、主类

一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类,并且只有主类可以用public修饰,其余类不得用public修饰。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。

11、Java和C++的区别

  • 都是面向对象的语言,都支持封装、继承和多态

  • Java不提供指针来直接访问内存,程序内存更加安全

  • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。

  • Java有自动内存管理机制,不需要程序员手动释放无用内存

二、基础语法

1、数据类型

1.java有哪些数据类型

定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

分类

  • 基本数据类型

    • 数值型

      • 整数类型(byte,short,int,long)

      • 浮点类型(float,double)

    • 字符型(char)

    • 布尔型(boolean)

  • 引用数据类型

    • 类(class)

    • 接口(interface)

    • 数组([])

2. Math.round函数

Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整

3.float f=3.4;是否正确

不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;

2、编码

Java语言采用何种编码方案?有何特点?

Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

3、访问修饰符

访问修饰符 public,private,protected,以及不写(默认)时的区别 定义:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

分类

private : 在同一类内可见。使用对象:成员变量、成员方法。 注意:不能修饰外部类,可以用来修饰内部类(因为内部类就相当于一个变量),私有针对的是类而不是对象,也就是说同一个类的不同对象之间可以相互访问对方的私有成员。特别的我们主张将成员变量设置为private并为外界提供get和set方法对成员变量进行访问,这种做法充分体现了封装思想

default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

protected : 对同一包内的所有类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

public : 对所有类可见。使用对象:类、接口、变量、方法

4、关键字

1. final

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

  • 被final修饰的类为最终类不可以被继承

  • 被final修饰的方法不可以被重写

  • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的

2.this

this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

this的用法在java中大体可以分为3种:

  • 普通的直接引用,this相当于是指向当前对象本身。

  • 形参与成员名字重名,用this来区分:

  • 引用本类的构造函数

3.super

super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

super也有三种用法:

  • 普通的直接引用

    与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。

  • 子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分

  • 引用父类构造函数

    • super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。

    • this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

4.super和this的区别
  • super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)

  • this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)

  • super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。

  • super()和this()均需放在构造方法内第一行。

  • 尽管可以用this调用一个构造器,但却不能调用两个。

  • this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。

  • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。

  • 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

5.static存在的主要意义

static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!

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

为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

6.static的独特之处

被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。 怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?

在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。 static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!

被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。例如普通方法需要创建对象才能访问,而static修饰的方法不用创建对象也可以直接访问。

7. static应用场景

因为static是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量。

因此比较常见的static应用场景有:

  • 修饰成员变量 。

    不能修饰局部变量,这是Java语法的规定。并且被static修饰的变量是被一个类中的所有对象所共有的,属于类,所以也称类变量,可以直接通过类名来引用

  • 修饰成员方法

  • 静态代码块

  • 修饰类【只能修饰内部类也就是静态内部类】

  • 静态导包 例如:

    在没有静态导入包前,使用最大值比较函数,是这样写:

    //头部没有静态引入Math类 Math.max(1,2);

    静态导包后,

    import static java.lang.Math.*; …… //头部静态引入了Math类 max(1,2);

    可以使得代码更清晰,代码量也减少了。

8. static注意事项
  1. 静态只能访问静态。

  2. 非静态既可以访问非静态的,也可以访问静态的。

5、流程控制语句

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

break 跳出总上一层循环,不再执行循环(结束当前的循环体)

continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)

return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

2. 在 Java 中,如何跳出当前的多重嵌套循环

在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:

 public static void main(String[] args) {
     ok:
     for (int i = 0; i < 10; i++) {
         for (int j = 0; j < 10; j++) {
             System.out.println("i=" + i + ",j=" + j);
             if (j == 5) {
                 break ok;
             }
 ​
         }
     }
 ​
 }

三、面向对象

1、面向对象概述

1.面向对象和面向过程的区别
面向过程:

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点:没有面向对象易维护、易复用、易扩展

面向对象:

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

缺点:性能比面向过程低

面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。

面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。

面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。

2.面向对象三大特性

封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。

继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。

关于继承如下 3 点请记住:

子类拥有父类非 private 的属性和方法。

子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

子类可以用自己的方式实现父类的方法。

多态性:

  • 概念:同一个类的实例在不同情形下的不同表现形式

3.面向对象的特征

抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

封装

封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

关于继承如下 3 点请记住:

子类拥有父类非 private 的属性和方法。 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。(以后介绍)。 多态

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

4.多态的实现

Java实现多态有三个必要条件:继承、重写、向上转型。

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

向上转型:子类对象可以赋给父类对象引用(Parent p = new Child())。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

5.面向对象五大基本原则是什么
  • 单一职责原则SRP(Single Responsibility Principle) 类的功能要单一,不能包罗万象,跟杂货铺似的。

  • 开放封闭原则OCP(Open-Close Principle) 一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。

  • 里式替换原则LSP(the Liskov Substitution Principle LSP) 子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~

  • 依赖倒置原则DIP(the Dependency Inversion Principle DIP) 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。

  • 接口分离原则ISP(the Interface Segregation Principle ISP) 设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

2、类与接口

1.抽象类和接口的对比
什么是抽象类?

抽象类就是用abstract修饰,且不能被直接初始化的类,但是可以通过子类来初始化 比如:Father father = new Son()

对应的,抽象方法就是用abstract修饰的方法

抽象方法是一种很特殊的方法,它没有方法体,即方法实现代码为空,比如abstract public void fun();

抽象方法一般在子类中进行实现,它就好像是在说:我不写代码,我只是声明一个方法名,剩下的交给我的子孙后代(继承类)去做

抽象类有一个很重要的特点:抽象类可以没有抽象方法,但是如果一个类有抽象方法,那么这个类肯定是抽象类

为什么会有抽象类?

解耦,使代码结构更加清晰

因为抽象类不能被直接创建为对象,它只是作为一个通用接口来供别人实现和调用,所以这样就使得抽象的代码更加清晰(它只声明方法,不实现方法) 就好比,老板和员工,老板负责分发任务,员工负责去具体的实现任务

什么是接口?

在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口的注意事项
  • 接口中包含有类要实现的方法,除非实现接口的类是抽象类,否则该类要实现接口中所有的方法。抽象类可不用全部实现,可由其子类实现(子类中的就是未实现的方法)。

  • 接口不能被实例化,但是可以实现。

  • 普通类实现接口,类中的方法也可以是空方法体,但必须有方法体也就是{}并且显示的用public 修饰符修饰方法

  • 接口体中有两种成员:全局静态变量(public static final 可以省略) 公共的抽象方法(默认用public abstract 修饰 可省略)

相同点

接口和抽象类都不能实例化 都位于继承的顶端,用于被其他实现或继承 都包含抽象方法,其子类都必须覆写这些抽象方法

不同点

备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。

现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。

接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。

2. 普通类和抽象类的区别
  • 普通类不能包含抽象方法,抽象类可以包含普通方法,也就是说抽象类中不一定全是抽象方法。

  • 抽象类不能直接实例化,普通类可以直接实例化。

3. 抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

4.创建一个对象用什么关键字?对象实例与对象引用有何不同?

new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)

5、类的注意事项

类也是一种数据类型,你自定义的数据类型,它可以包含数据和各种实现

3、变量与方法

1.成员变量和局部变量
定义

变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域

成员变量:方法外部,类内部定义的变量

局部变量:类的方法中的变量。局部变量本质上其实就是一个访问权限限定符。

区别

作用域

成员变量:针对整个类有效。 局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)

存储位置

成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。

生命周期

成员变量:随着对象的创建而存在,随着对象的消失而消失 局部变量:当方法调用完,或者语句结束后,就自动释放。

初始值

成员变量:有默认初始值。

局部变量:没有默认初始值,使用前必须赋值。

使用原则

在使用变量时需要遵循的原则为:就近原则 首先在局部范围找,有就使用;接着在成员位置找。

6.静态变量和实例变量区别

静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。

实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

7.静态变量与普通变量区别

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。

8.静态方法和实例方法有何不同?
  • 静态方法和实例方法的区别主要体现在两个方面:

  • 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制

9. 在一个静态方法内调用一个非静态成员为什么是非法的?

由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。

10. 什么是方法的返回值?返回值的作用是什么?

方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!

11、抽象方法

抽象方法用abstract修饰,是需要声明,不需要实现

4、内部类

1. 什么是内部类?

在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

2. 内部类的分类有哪些

内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。

静态内部类

定义在类内部的静态类,就是静态内部类。

成员内部类

定义在类内部,成员位置上的非静态类,就是成员内部类。

局部内部类

定义在方法中的内部类,就是局部内部类。

匿名内部类

匿名内部类就是没有名字的内部类,日常开发中使用的比较多。

除了没有名字,匿名内部类还有以下特点:

  • 匿名内部类必须继承一个抽象类或者实现一个接口。

  • 匿名内部类不能定义任何静态成员和静态方法。

  • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。

  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

3. 内部类的优点

我们为什么要使用内部类呢?因为它有以下优点:

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!

  • 内部类不为同一包的其他类所见,具有很好的封装性;

  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。

  • 匿名内部类可以很方便的定义回调。

4. 内部类有哪些应用场景
  1. 一些多算法场合

  2. 解决一些非面向对象的语句块。

  3. 适当使用内部类,使得代码更加灵活和富有扩展性。

  4. 当某个类除了它的外部类,不再被其他的类使用时。

5. 内部类相关,看程序说出运行结果

public class Outer { private int age = 12;

 class Inner {
     private int age = 13;
     public void print() {
         int age = 14;
         System.out.println("局部变量:" + age);
         System.out.println("内部类变量:" + this.age);
         System.out.println("外部类变量:" + Outer.this.age);
     }
 }
  
 public static void main(String[] args) {
     Outer.Inner in = new Outer().new Inner();
     in.print();
 }

}

结果:

局部变量:14 内部类变量:13 外部类变量:12

5、重写与重载

1. 重载(Overload)和重写(Override)的区别。

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。

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

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

6、对象相等判断

1.== 和 equals 的区别是什么

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)

equals() : 它的作用也是判断两个对象(内容)是否相等。但它一般有两种使用情况:

情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。

情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

说明:

String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。

2.hashCode 与 equals (重要)

HashSet如何检查重复

两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

hashCode和equals方法的关系

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

hashCode()介绍

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

为什么要有 hashCode

我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

hashCode()与equals()的相关规定

如果两个对象相等,则hashcode一定也是相同的

两个对象相等,对两个对象分别调用equals方法都返回true

两个对象有相同的hashcode值,它们也不一定是相等的

因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

7、值传递

1. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的

2.为什么 Java 中只有值传递

首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。

Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

3.值传递和引用传递有什么区别

值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。

引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

8、Java包

1. JDK 中常用的包有哪些
  • java.lang:这个是系统的基础类;

  • java.io:这里面是所有输入输出有关的类,比如文件操作等;

  • java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;

  • java.net:这里面是与网络有关的类;

  • java.util:这个是系统辅助类,特别是集合类;

  • java.sql:这个是数据库操作的类。

2. import java和javax有什么区别

刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。

所以,实际上java和javax没有区别。这都是一个名字。

9、异常类

1、异常类的父类

Java语言中所有的异常类的父类是Throwable类,而Error和Exception是Throwable类的子类,而所有的异常类都是继承自Excepiton类。

2、异常的作用

作用一:异常是用来查询bug的关键参考信息

作用二:异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况

3、异常的常用方法

public String getMessage() :返回此throwable的详细信息

public String toString() :返回此可抛出的简短描述

public void printStackTrace() :把异常的错误信息输出在控制台

4、抛出异常
throw

写在方法内,表示结束方法的。手动抛出异常对象,把异常交给调用者处理。方法中下面的代码便不再执行了

throws

写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常

public void 方法() throws 异常类名1,异常类名2..{

...

}

5、捕获和抛出
捕获

捕获是定义在方法定义处的,核心是不让程序停止

抛出

抛出大多都是些在方法里的,核心是告诉调用者方法出错了

6、异常的处理

当程序出现异常时,Java虚拟机默认将自动结束程序,所以我们需要自己来处理异常。

异常处理的方案
try...catch...

try{

可能异常的代码

}catch(异常类名 变量名){

异常处理代码;

}

7、异常注意点
  • 并不是所有的异常都需要捕获

  • 异常也是一个对象

10、数组

1、数组的定义

int [] a = new int[n] :定义一个int类型的数组,数组的名字为a,数组的元素个数为n

Student [] s = new Student[n]:定义一个student类型的数组,数组名字为s

2、数组中元素类型

数组中的元素类型可以是简单数据类型的还可以是对象类型

11、构造方法

1、构造方法的作用

对对象进行初始化.

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

Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

3. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

帮助子类做初始化工作。

4. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?

主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

5. 构造方法有哪些特性?

名字与类名相同

没有返回值,但不能用void声明构造函数

生成类的对象时自动执行,无需调用

不能被继承。当子类没有构造方法时,才会调用父类的构造方法

12、对比记忆

1、length和size
  • length() :针对的是字符串,要求一个字符串的长度时用到此方法

  • length属性:针对是java数组,求数组长度时使用

  • size(): 针对是泛型集合来,也就是List

2、Builder和Buffer
  • 两者都能对字符串进行修改

  • Builder线程不安全,Buffer增加了同步锁,所以线程安全(可以同步访问)

  • 正常情况下builder的性能更快,但是在要求线程安全的情况下只能用buffer

四、IO流

1、java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;

  • 按照操作单元划分,可以划分为字节流和字符流;

  • 按照流的角色划分为节点流和处理流。

Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。

InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

2、BIO,NIO,AIO 有什么区别?

简答

  • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。

  • NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

  • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

3、Files的常用方法都有哪些?

  • Files. exists():检测文件路径是否存在。

  • Files. createFile():创建文件。

  • Files. createDirectory():创建文件夹。

  • Files. delete():删除一个文件或目录。

  • Files. copy():复制文件。

  • Files. move():移动文件。

  • Files. size():查看文件个数。

  • Files. read():读取文件。

  • Files. write():写入文件。

五、反射

1、什么是反射机制?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

静态编译和动态编译

静态编译:在编译时确定类型,绑定对象 动态编译:运行时确定类型,绑定对象

2、反射机制优缺点

  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。

  • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

3、 反射机制的应用场景有哪些?

反射是框架设计的灵魂。

在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

4、Java获取反射的三种方法

1.通过new对象实现反射机制

2.通过路径实现反射机制

3.通过类名实现反射机制

六、网络编程

1.概述

计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来的,实现资源共享和数据传输的系统。网络编程就是编写程序使互联网的两个(或多个)设备(如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持。通过其提供的接口我们可以很方便地进行网络编程。

2. Java Socket网络编程

1.socket概述

Java的网络编程主要涉及到的内容是Socket编程。Socket,套接字,就是两台主机之间逻辑连接的端点。TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

Socket,实际上是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。实际上,Socket跟TCP/IP协议没有必然的关系,Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现,只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、listen、accept、send、read和write等等。网络有一段关于socket和TCP/IP协议关系的说法比较容易理解:

“TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”

实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。socket是对端口通信开发的工具,它要更底层一些。

2. Socket整体流程

Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。

客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。

七、进程

线程和进程

线程
概念
  • 线程是操作系统能够进行运算调度的最小单位。他被包含在进程中,是进程中的实际运行单位

  • 一个进程最少有一个线程,一个进程可以运行多个线程。多个线程的之间共享数据

作用
  • 我们可以通过它进行多处理器编程

  • 也可以使用多线程对运算密集型任务提速,也就是并发处理

进程
  • 进程是程序的基本执行实体

  • 一个在内存中运行的应用程序就是一个进程。

  • 每个进程都有自己独立的一块内存空间,一个进程可以有多个线程

什么是多线程

  • 多线程是指再在单个进程内同时运行多个线程的编程模型

多线程的实现

1.继承Thread类并重写run方法
  • 为什么要重写run方法?

    因为run是用来封装被线程执行的代码

  • run和start方法的区别

run:封装线程执行的代码,直接调用,相当于普通方法的调用

start:启动线程,然后由JVM调用此线程的run方法

  • 缺点:继承性差,因为java类是单继承的,已经继承了Thread就不能在继承其他类了

2.实现runnable接口
  • 定义一个类实现runnable接口

  • 在新的类中重写run方法

  • 创建类对象

  • 创建线程对象,将自定义的对象作为参数传入

  • 启动线程

3.利用Callable接口和Future接口方式实现
  • 创建一个类实现Callable实现Callable接口

  • 重写call()方法,有返回值,表示多线程运行的结果

  • 创建类对象(表示多线程要执行的任务)

  • 创建FutureTask对象(管理多线程结果)

  • 创建Thread类对象并启动(表示线程)

4.线程池创建线程

并发和并行

  • 并发:多个命令交替执行(一会儿执行1,一会执行2)

  • 并行:多个命令在多个CPU上同时执行

在计算机中,并发和并行可以同时进行的

常见的成员方法

方法名称说明
String getName()返回此线程的名字
void setName(String name)设置线程的名字
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让现场休眠指定的时间,单位为毫秒
stePriority(int newProiorty)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()礼让线程
public static void join()插入线程

守护线程

  • 两个线程,一个设置为守护,一个为正常。两者会同时执行,但当正常线程结束以后,守护也会相应的结束,无论这个线程是否执行完毕

礼让线程

  • 出让当前CPU的执行权,在run方法里调用

插入线程

  • 把线程插入到当前线程之前

线程的状态

新建状态(NEW)
  • 线程已经构建完成,也就是已经new出来了,但是尚未启动

可执行状态(Runnable)
  • 可执行状态包括就绪和运行

就绪状态
  • 当调用start方法启动线程后,这个时候线程有了执行资格,但是没有执行权

运行状态
  • 当线程就绪了以后,就会去抢占CPU的执行权,抢到了执行权后就处于运行状态

阻塞状态(BLOACKED)
  • 线程被锁阻塞,无法获得锁。

  • 当获得锁之后,就变为了就绪状态

等待状态(WAITING)
  • 线程需要其他线程做出特定动作(通知或者中断),比如调用wait()方法

时间等待状态
  • 不等同于等待状态,超时等待状态可以在指定的时间内自行返回,比如调用了sleep方法

死亡状态
  • 当前线程已经执行完毕

线程安全

  • 线程的执行是有随机性的,可能造成错误的结果

  • 有的时候我们想让线程只要他的代码没有结束,那么CPU的执行权就一直在它上。

解决方法
  • 将执行的代码锁起来,不让别的线程来操作

 synchronized(锁){
     操作的共享数据代码
 }

而这个锁可以是任意对象,但一定是唯一的

我们也可以将要循环的代码抽取出来,封装成一个方法,在方法的修饰符后面加上synchronized是一样的效果

Lock锁

概述
  • Lock锁是一个接口,创建的时候只能够创建它的实现类ReentrantLock

语法
 import java.util.concurrent.locks.ReentrantLock;
 ​
 private final ReentrantLock lock = new ReentrantLock();
 ​
 public void someMethod() {
     //加锁
     lock.lock();
     try {
         // 同步的代码块
     } finally {
         //解锁
         lock.unlock();
     }
 }

死锁

  • 死锁(Deadlock)是多线程编程中常见的问题,它发生在两个或多个线程相互等待对方释放资源或锁时,导致程序无法继续执行的状态

  • 死锁通常发生在多个线程同时持有一些资源,并且每个线程试图获取其他线程持有的资源时

避免死锁的策略
  • 按顺序获取锁:确保所有线程以相同的顺序获取锁,从而避免循环等待条件

  • 使用超时机制:在尝试获取锁时,设置一个最大等待时间,如果超过该时间仍未获得锁,线程可以放弃或尝试其他操作

  • 使用死锁检测和恢复机制:定期检测是否存在死锁,并在检测到死锁时采取措施,如终止某些线程或回滚操作

线程池

引出
  • 当线程要用到的时候就主动创建,代码执行完以后,线程就消失。这种方法是不对的,太耗资源

解决方法
  • 引出线程池,任务完成以后就将线程放入线程池中,当需要执行一个新的任务时,在调用线程池中的线程。

  • 当一个新的任务进来时,而这个时候线程池中的线程都被占用了,这个时候再去创建新的线程

线程池的实现
创建线程池

创建一个没有上限的线程池

 public static ExecutorService newCachedThreadPool()

创建有上限的线程池

 public static ExecutorService newFixedThreadPool()
提交任务
任务执行完毕,关闭线程池

八、常用API

1、String相关

1.字符型常量和字符串常量的区别
  • 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符

  • 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)

  • 占内存大小:字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)

2. 什么是字符串常量池?

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

3. String 是最基本的数据类型吗?

不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。

这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char[] chars = {‘你’,‘好’};

但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。

4.String有哪些特性

不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。

常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。

final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

5.String为什么是不可变的吗?

简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:

/** The value is used for character storage. */ private final char value[];

6. String真的是不可变的吗?

我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:

  1. String不可变但不代表引用不可以变

String str = "Hello"; str = str + " World"; System.out.println("str=" + str); 结果:

str=Hello World 解析:

实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。

7.是否可以继承 String 类

String 类是 final 类,不可以被继承。

8. String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

9. String s = new String(“xyz”);创建了几个字符串对象

两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

10. 如何将字符串反转?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

11. 数组有没有 length()方法?String 有没有 length()方法

数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。

12. String 类的常用方法都有那些?
  • indexOf():返回指定字符的索引。

  • charAt():返回指定索引处的字符。

  • replace():字符串替换。

  • trim():去除字符串两端空白。

  • split():分割字符串,返回一个分割后的字符串数组。

  • getBytes():返回字符串的 byte 类型数组。

  • length():返回字符串长度。

  • toLowerCase():将字符串转成小写字母。

  • toUpperCase():将字符串转成大写字符。

  • substring():截取字符串。

  • equals():字符串比较。

13. 在使用 HashMap 的时候,用 String 做 key 有什么好处?

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

14. String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

可变性

String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

线程安全性

String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的(不能同步访问)。

性能

每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结

  • 如果要操作少量的数据用 = String

  • 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

  • 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

  • 两者都能对字符串进行修改

  • 正常情况下builder的性能更快,但是在要求线程安全的情况下只能用buffer

2、Date相关

3、包装类相关

1. 自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;

拆箱:将包装类型转换为基本数据类型;

2. int 和 Integer 有什么区别

Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

Java 为每个原始类型提供了包装类型:

原始类型: boolean,char,byte,short,int,long,float,double

包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false

 Integer a1 = 128;
 Integer b1 = 128;
 System.out.println(a1 == b1);//false
 ​
 Integer a2 = 127;
 Integer b2 = 127;
 System.out.println(a2 == b2);//true

超过127时会重新new一个对象,所以a1和b1不相等

九、集合框架

总览

List族

什么是List
什么是集合
  • 集合就是把具有相同属性的东西放在一起,也可以是容器,把有关的东西都放进去

什么是List
  • List是位于java.util下的一个接口,有序、可重复集合(也称为序列)。此界面的用户可以精确控制每个元素在列表中的插入位置。用户可以通过整数索引(列表中的位置,类似于数组元素的下标)访问元素,并在列表中搜索元素

  • 它继承自Collection接口,而Collection接口是 Java 集合框架的根接口,定义了基本的集合操作和属性。

  • 他是Java集合框架中的一部分

List的继承、实现关系

其继承了Collection接口并由AbstractList来实现,Collection又继承了Iterable接口

  • Collection:Java集合层次结构中的根接口。一个集合表示一组对象,称为它的元素。一些集合允许重复元素,而另一些则不允许。有些是有序的,有些是无序的。

  • Iterable:实现此接口允许对象成为“for-each 循环”语句的目标。

List的种类
ArrayList
特性
  • java中的一个类

  • 底层由数组结构实现Object[],可以存储任何Object类型的对象,是非线程安全的

  • 由无参构造函数创建

  • 由ArrayList的源码可见,当创建ArrayList对象时会自动创建初始容量为10的空列表,并且可以随着需要而增长的动态数组

优点
  • 遍历元素和随机访问元素的效率比较高

缺点
  • 添加和删除需要大量移动元素效率低,按照内容查询效率低

因为arrayList的底层是基于数组作为数据存储结构的。当向ArrayList增加一个元素是,如果当前数组已满,那么系统就会创建一个新的更大的数组,然后将原数组的数据转移到新的数组里面,这样就会很耗费时间以及性能较低。当删除元素时,如果元素位于数组中间,那么系统会删除这个元素然后将位于这个元素后面的所有元素都向前移动一格,影响效率

LinkedList
  • List和Deque接口的双向链表实现。实现所有可选列表操作,并允许所有元素(包括null )。所有操作都按照双向链表的预期执行。索引到列表中的操作将从开头或结尾遍历列表,以更接近指定索引的为准

  • 由无参构造函数创建

  • 初始化为空

优点
  • 插入、删除元素时效率比较高

缺点
  • 遍历和随机访问元素效率低下

因为LinkedList底层是基于链表的。每一个元素都包含了一个指向前一个元素和后一个元素的引用,通过引用这些连接元素形成链表。当需要查询特定位置时,LinkedList需要从头结点开始依次遍历直到找到目标元素。而相比较之下,ArrayList则可以直接通过索引直接访问元素,效率更高

Vector
  • 底层实现是动态数组的方式存放数据,是线程安全的,Vector源码当中每个方法都被synchronized关键字进行修饰,保证了Vector的线程安全,所以效率很低,尽量少使用

  • 与ArrayList类似,默认容量为10

  • 使用有参的构造函数可以指定容量的大小

Set族

介绍
  • 接口

  • Set是不包含重复元素的Collection,这是非常重要的一个特性

HashSet
  • HashSet是完全基于HashMap实现的,也就是数组+链表或者红黑树

  • 存储无序,无下标,元素不重复数据

  • 支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的

LinkedHashSet
  • HashSet的一个子类

  • 底层是链表+哈希表

  • 具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序

TreeSet
  • 底层的数据结构是红黑树

  • 支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)

Map族

介绍
  • 接口

  • Map 是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个 Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。

  • 当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求,你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)

TreeMap
  • 基于红黑树实现

HashMap
  • 基于哈希表

HashTable
  • 和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。

LinkedHashMap
  • 使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序

十、泛型

引出

我们现在有一个需求,需要给一个接口传参,但是问题在于我们不知道参数的具体类型是什么,所以我们可能会去编写多个接口,每一个接口负责不同类型的传参,但是这样代码的利用率不高,代码冗余严重

有没有一种不需要指定的具体类型的参数呢,只需要当我们在调用它的时候在去决定即可

这个时候泛型就诞生了

定义

泛型(Generics)

  • 顾名思义,就是广泛的数据类型,什么数据类型都可以,一般我们用T、K、V这些来表示

基本用法

如何声明一个泛型类呢?

  • 在类名和大括号之间定义,用<>包裹起来就行

 class Printer <T>{
     T content;
     Printer(T content){
         this.content = content;
     }
     public void print(){
         System.out.println(content);
     } 
 }
 public static void main(String args[]){
     Printer<String> p = new Printer<>("hello world");
     Printer<Integer> p2 = new Printer<>(123);
     p.print();
     p2.print();
 }
  • 其中泛型的单数类型必须是类,包括Integer、Boolean等引用数据类型(万物皆对对象)或者对象,但不能是基本数据类型,因为基本数据类型不能是类

传入多个参数

 class Printer <T,K>{
     T content;
     K content2;
     Printer(T content,K conten2){
         this.content = content;
         this.conten2 = conten2;
     }
     public void print(){
         System.out.println(content);
         System.out.println(content2);
     } 
 }
 public static void main(String args[]){
     Printer<String,Integer> p = new Printer<>("hello world",123);
     p.print();
 }

参数约束

当我们指定这个参数必须是某一个参数的子类型时

 class Printer <T extends People>{
     T content;
     Printer(T content){
         this.content = content;
     }
     public void print(){
         System.out.println(content);
     } 
 }
 public static void main(String args[]){
     Printer<Student> p = new Printer<>(new Student());
     p.print();
 }

如果参数约束为接口,也一样使用extends

 class Printer <T extends People & Thing>{
     T content;
     Printer(T content){
         this.content = content;
     }
     public void print(){
         System.out.println(content);
     } 
 }
 public static void main(String args[]){
     Printer<Student> p = new Printer<>(new Student());
     p.print();
 }

这个例子就表示参数必须是People的子类而且必须实现Thing接口,class必须放在接口前面

方法泛型

当我们需要在一个方法上定义一个泛型的参数时,我们需要在方法返回值前面加一个<T>

 class Printer <T extends People & Thing>{
     T content;
     Printer(T content){
         this.content = content;
     }
     public void print(){
         System.out.println(content);
     } 
 }
 public static void main(String args[]){
     Printer<Student> p = new Printer<>(new Student());
     p.print();
 }
 private static <T> void print(T content){
     System.out.println(content);
 }

如果不加<T>的话java会无法识别参数里面的T的,加了<T>以后就是告诉java这里的T是一个泛型,同时我们也可以添加一些约束<T extends People & Thing>,同时也可以传入多个参数<T,K>

通配符

当我们想要引入一个List类型的参数时,我们也是需要去改List里面的具体类型的,但是这样显然很麻烦,有没有一种能代替所有类型的方法呢?

  • 这个时候我们就引入了通配符的概念

 class Printer <T extends People & Thing>{
     T content;
     Printer(T content){
         this.content = content;
     }
     public void print(){
         System.out.println(content);
     } 
 }
 public static void main(String args[]){
     List<String> list = new ArrayList<>();
     list.add(123);
     list.add("hello world");
     print(list);
 }
 private static void print(List<?> content){
     System.out.println(content);
 }

当我们同样想要加入一些约束时,我们可以加入<? extends People & Thing>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值