很多小伙伴在学习了Java一段时间后,不可避免的要遇到面试的问题 所以本贴是给大家开的新地图哈哈哈 主要是一些面试中比较容易被问到的问题,逐步更新中~
1. 面向对象的特征有哪些方面?
答:面向对象的特征主要有以下几个方面: 1.封装: 封装是把过程和数据包裹起来,外界对于数据的操作仅限于我们提供的方式。我们可以将生活中各种事物进行抽象封装形成类,也可以通过private修饰符来封装属性与方法,不过需要注意的是,封装后需要手动提供对应被封装资源对外公共的操作方式。
2.继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。我们可以从现有的类中派生出一个新的类,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
3.多态性:多态性是指可以尽量屏蔽不同类的差异性,提供通用的解决方案。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。我们所使用的有: 向上造型【将子类对象统一看作父类型,比如花木兰替父从军】 向下造型【将之前看作父类型的子类对象再重新恢复成子类对象,比如花木兰打仗结束回家化妆】
4.抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
2. 抽象类与接口的异同:
抽象类: 抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。 抽象类可以拥有成员变量和普通的成员方法。
抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。 2)抽象类不能用来创建对象; 3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
接口:
接口中的变量会被隐式地指定为public static final变量,并且只能是public static final变量,用private修饰会报编译错误,而方法会被隐式地指定为public abstract方法且只能是public abstract方法,用其他关键字,比如private、protected、static、 final等修饰会报编译错误,并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。
抽象类和接口的区别:
语法层面上的区别 1)一个类只能继承一个抽象类,而一个类却可以实现多个接口 2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的 3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法 4)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法 5)抽象类的抽象方法可以是public,protected,default类型,而接口的方法只能是public 设计层面上的区别 1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象 所以抽象是重构的结果,接口是设计的结果。 2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
3. 简单说一下常见的数据结构:
1.栈Stack: 又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。 我们可以简单理解成:栈结构对元素的存取有如下要求: 先进后出(也就是说,栈中越早存进去的元素,就越在最下面) 例如:子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。 所以,栈的入口、出口的都是栈的顶端位置。我们需要知道两个名词: 1)压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。 2)弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
2.队列queue: 又称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。 我们可以简单理解成:队列结构对元素的存取有如下要求: 先进先出(也就是说,最先存进去的元素,是可以最先取出的) 例如:我们生活中排队买结账,最先到的人最先付款,最后到的人得等前面的人都付款结束后才能付款。 所以:队列的入口、出口各占一侧
3.数组Array: 是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一列火车,从1号车厢到最后一节车厢,每节车厢都有自己的固定编号,乘坐火车的人可以通过车厢号快速找到自己的位置。 我们可以简单理解成:数组结构对元素的存取有如下要求: 查找元素快:通过索引,可以快速访问指定位置的元素。 增删元素慢: 我们指定一个索引增加元素,不能修改原数组的长度,而是需要创建一个新长度的数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。 我们指定一个索引删除元素:不能修改原数组的长度,而是需要创建一个新长度的数组,把原数组元素根据索引复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。
4.链表LinkedList: 是一系列节点Node(链表中每一个元素称为节点)组成,节点可以在运行时动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域。我们常说的链表结构有单向链表与双向链表。 我们这里介绍的是单向链表。 我们可以简单理解成:链表结构对元素的存取有如下要求: 多个节点之间,通过地址进行连接。 例如:自行车的链条,就是通过上一个环连接下一个环,这样就形成一个链条了 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素 增删元素快: 增加元素:只需要修改连接下个元素的地址即可。 删除元素:只需要修改连接下个元素的地址即可。
5.红黑树
二叉树BinaryTree:每个节点不超过2的有序树(tree) 我们可以简单理解成生活的树的结构,只不过每个节点上都最多只能有两个子节点。 二叉树是每个节点最多有两个子树的树结构。顶上的叫根节点,两边被称作“左子树”和“右子树”。 红黑树:红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。红黑树的速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
红黑树的特点:
- 节点可以是红色的或者黑色的
- 根节点是黑色的
- 叶子节点(特指空节点)是黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
4. 什么是向上转型?什么是向下转型?
在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。 那么在这个过程中就存在着多态的应用。存在着两种转型方式,分别是:向上转型和向下转型。 向上转型:可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。 比如:父类Parent,子类Child 父类的引用指向子类对象:Parent p=new Child(); 说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类中声明过的方法,方法体执行的就是子类重过后的功能。但是此时对象是把自己看做是父类类型的,所以其他资源使用的还是父类型的。 比如:花木兰替父从军,大家都把花木兰看做她爸,但是实际从军的是花木兰,而且,花木兰只能做她爸能做的事,在军营里是不可以化妆的。
向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。这个是之前向上造型过的子类对象仍然想执行子类的特有功能,所以需要重新恢复成子类对象 Parent p = new Child();//向上转型,此时,p是Parent类型 Child c = (Child)p;//此时,把Parent类型的p转成小类型Child 其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的 说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。 比如:花木兰打仗结束,就不需要再看做是她爸了,就可以”对镜贴花黄”了
5. 比较一下String与StringBuilder
String
1. 特点:
创建之后长度内容是不可变的,每次拼接字符串,都会产生新的对象
- 如果是直接“ ” 或者字符串常量拼接产生的,保存在字符串常量池中
- 如果是直接通过new方式创建的,保存在堆中
2. 创建方式:
String() String(String s) String(char[] c) String(byte[] b) String s = “abc”;
3. 优缺点:
- 优点:String类提供了丰富的关于操作字符串的方法,比如:拼接、获取对应下标处的字符、截取子串等等
- 缺点:在进行字符串拼接+=的时候,效率比较低
4. String转StringBuilder:
String s = “abc”; StringBuilder sb = new StringBuilder(s);
StringBuilder
1. 特点:
StringBuilder是一个长度可变的字符串序列,在创建的时候,会有一个长度为16的默认空间 当拼接字符串的时候,实在原对象的基础之上进行拼接,如果长度不够就扩容 所以StringBuilder在创建之后,对应的操作一直是用一个对象
2. 创建方式:
StringBuilder sb = new StringBuilder();//创建一个长度为16的StringBuilder对象 StringBuilder sb = new StringBuilder(“abc”);//以指定字符串内容为“abc”的方式创建一个StringBuilder对象
3. 优缺点:
- 优点:在拼接的时候,不会产生新的对象,就避免了因为拼接频繁生成对象的问题,提高了程序的效率
- 缺点:对于字符串的操作,不太方便,所以在使用的时候,如果拼接操作很多的话:
先将String转为StringBuilder进行拼接,拼接完成之后再转回String
4. StringBuilder转String:
StringBuilder sb = new StringBuilder(); sb.append(“abc”); String s = sb.toString();
6.什么是泛型通配符?什么是泛型的上下限?
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
通配符高级使用----受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。 泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称 - 意义:
只能接收该类型及其子类
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称 - 意义:
只能接收该类型及其父类型
7. 线程有几种状态?它们是怎么切换的?
线程生命周期,主要有五种状态:
- 新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
- 就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行 - 运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态 - 阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种: 等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态 其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态 - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
就绪 → 执行:为就绪线程分配CPU即可变为执行状态" 执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态 执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞 (例如线程正在访问临界资源,而资源正在被其他线程访问) 反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行
8.什么是线程池?线程池有什么优点?
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。 所以我们在在Java中可以通过线程池来避免这些问题: 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
9. 你对Java内存分配了解多少?简单谈谈:
Java虚拟机管理的内存包括几个运行时数据内存:方法区、虚拟机栈、本地方法栈、堆、程序计数器,其中方法区和堆是由线程共享的数据区,其他几个是线程隔离的数据区
1. 程序计数器
程序计数器是一块较小的内存,他可以看做是当前线程所执行的行号指示器。字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节码的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器则为空。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemotyError情况的区域
2 Java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 栈内存就是虚拟机栈,或者说是虚拟机栈中局部变量表的部分 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(refrence)类型和returnAddress类型(指向了一条字节码指令的地址) 其中64位长度的long和double类型的数据会占用两个局部变量空间,其余的数据类型只占用1个。 Java虚拟机规范对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
3 本地方法栈
本地方法栈和虚拟机栈发挥的作用是非常类似的,他们的区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务 本地方法栈区域也会抛出StackOverflowError和OutOfMemoryErroy异常
4 Java堆
堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。所有的对象实例和数组都在堆上分配 Java堆是垃圾收集器管理的主要区域。Java堆细分为新生代和老年代 不管怎样,划分的目的都是为了更好的回收内存,或者更快地分配内存 Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有完成实例分配,并且堆也无法再扩展时将会抛出OutOfMemoryError异常
5 方法区
方法区它用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 除了Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载 当方法区无法满足内存分配需求时,将抛出OutOfMemoryErroy异常 1.6 运行时常量池 它是方法区的一部分。Class文件中除了有关的版本、字段、方法、接口等描述信息外、还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放 Java语言并不要求常量一定只有编译期才能产生,也就是可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法 当常量池无法再申请到内存时会抛出OutOfMemoryError异常
10. 谈谈你对static的了解
static是Java中的一个关键字,可以用来修饰方法、变量、代码块、内部类,还可以使用静态导包
1.static方法
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
注意:虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
2.static变量
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本【存放在方法区】,它当且仅当在类初次加载时会被初始化【加final和不加final的static变量初始化的位置不一样】。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
3.static代码块
static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能,因为静态资源只会加载一次。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。 初始化的顺序 静态代码块 > 构造代码块 > 构造函数
为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。 下面看个例子:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好,其实就是利用了静态代码块在内存中值加载一次的机制:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
4.静态内部类
在谈论静态内部之前,我们来谈谈,为何要用内部类? 定义在一个类内部的类叫内部类,包含内部类的类称为外部类。内部类可以声明public、protected、private等访问限制,可以声明为abstract的供其他内部类或外部类继承与扩展,或者声明为static、final的,也可以实现特定的接口。内部类有以下特点: 1.内部类一般只为其外部类使用【比如:hashmap集合中,内部类Entry<K,V>】 2.内部类提供了某种进入外部类的窗户,内部类存在外部类的引用,所以内部类可以直接访问外部类的属性 3.每个内部类都能独立地继承一个接口,而无论外部类是否已经继承了某个接口。因此,内部类使多重继承的解决方案变得更加完整。
静态内部类 定义静态内部类:在定义内部类的时候,可以在其前面加上一个权限修饰符static。此时这个内部类就变为了静态内部类。通常称为嵌套类,当内部类是static时,意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象;
- 不能从嵌套类的对象中访问非静态的外围类对象(不能够从静态内部类的对象中访问外部类的非静态成员);
嵌套类与普通的内部类还有一个区别:普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段, 也不能包含嵌套类。但是在嵌套类里可以包含所有这些东西。也就是说,在非静态内部类中不可以声明静态成员,只有将某个内部类修饰为静态类,然后才能够在这 个类中定义静态的成员变量与成员方法。
另外,在创建静态内部类时不需要将静态内部类的实例绑定在外部类的实例上。普通非静态内部类的 对象是依附在外部类对象之中的,要在一个外部类中定义一个静态的内部类,不需要利用关键字new来创建内部类的实例。静态类和方法只属于类本身,并不属于 该类的对象,更不属于其他外部类的对象。
补充:内部类标识符 每个类会产生一个.class文件,文件名即为类名。同样,内部类也会产生这么一个.class文件,但是它的名称却不是内部类的类名,而是有着严格的限制:外围类的名字,加上$,再加上内部类名字。
5.静态导包
静态导包就是java包的静态导入,用import static代替import静态导入包是JDK1.5中的新特性。
一般我们导入一个类都用 import com……ClassName; 而静态导入是这样:import static com……ClassName.* ; 这里的多了个static,还有就是类名ClassName后面多了个.* ,意思是导入这个类里的静态方法。 当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。 然后在这个类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名的方式来调用。
好处:这种方法的好处就是可以简化一些操作,例如打印操作System.out.println(…); 就可以将其写入一个静态方法print(…),在使用时直接print(…)就可以了。 但是这种方法建议在有很多重复调用的时候使用,如果仅有一到两次调用,不如直接写来的方便
在静态导入之前:
public class TestStatic {
public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.toHexString(42));
}
}
在静态导入之后:
import static java.lang.System.out;
import static java.lang.Integer.*;
public class TestStaticImport {
public static void main(String[] args) {
out.println(MAX_VALUE);
out.println(toHexString(42));
}
}
让我们看一下使用静态导入特性的代码中将发生什么:
- 虽然该特性通常称为“静态导入”,但语法必须是import static,后面跟你想导入的static成员的完全限定名称,或者通配符。在本例中,我们在System类的out对象上进行静态导入。
- 在本例中,我们可能想使用java.lang.Integer类的几个static成员。该静态导入语句使用通配符来表达“我想在此类中的所有静态成员上进行静态导入”。
- 现在我们终于看到静态导入特性的好处!我们不必在System.out.println中键入System。太好了!另外,我们不必在Integer.MAX_VALUE中键入Integer。因此,在这行代码中,我们能够将快捷方式用于静态方法和一个常量。
- 最后,我们进行更多的快捷操作,这次针对Integer类的方法。
下面是使用静态导入的几条原则:
你必须说import static, 不能说static import。
提防含糊不清的命名static成员。例如,如果你对Integer类和Long类执行了静态导入,引用MAX_VALUE将导致一个编译器错误,因为Integer和Long都有一个MAX_VALUE常量,并且Java不会知道你在引用哪个MAX_VALUE。
你可以在static对象引用、常量(记住,它们是static 或final)和static方法上进行静态导入。
11.什么是方法?谈谈你对方法的理解
1.概念
方法其实是具有一定功能的代码块,我们可以把需要多次使用的功能提取成一个方法,这样多次使用的时候也不需要把这些重复的代码写多次造成代码的冗余。
2.格式
方法定义的格式:修饰符 返回值类型 方法名(参数列表){方法体} 方法签名:方法名(参数列表)
3.注意事项
注意:我们这里说的是返回值类型而不是返回值,如果一个方法有返回值,那么返回值类型必须设置为与返回值相同的类型,并且返回值需要使用return关键字来返回。
4.方法的重载:
在同一个类中出现方法名相同但参数列表不同方法的现象
注意:方法之间能否构成重载,取决于方法的参数个数与类型,与方法的参数名无关 我们可以通过方法名+参数列表的方式确定要调用的是哪个方法 方法的传值:基本类型传递的是实际值,引用类型传递的是地址
而且方法的参数属于形参,只是格式上需要定义,但是调用方法时起不到限制的作用 形参:定义方法的时候的参数列表 实参:使用方法的时候传入的数据
重载的意义: 是为了方便外界对方法进行调用,什么样的参数程序都可以找到对应的方法来执行,体现的是程序的灵活性
5.方法的重写:
子类继承父类以后,如果子类对父类的功能不满意,可以重写父类的方法
但是重写的时候需要注意如下的规则:两同两小一大 一大:子类方法的修饰符范围 >= 父类方法的修饰符范围–指的是访问控制符 两同:方法名相同,参数列表相同 两小: 子类方法的返回值类型 <= 父类方法的返回值类型【这个大小是继承关系,不是值的大小】 子类方法抛出的异常类型 <= 父类方法抛出的异常类型【这个还没学,不用管】 注意:如果父类方法的返回值类型是void,子类保持一致即可 注意:子类不可以重写父类的私有方法,还是因为不可见
重写的意义:是在不修改源码的前提下,进行功能的修改和拓展 (OCP原则:面向修改关闭,面向拓展开放)
6.方法的递归:
递归:在方法中调用自己本身 注意递归次数过多时,会出现栈溢出异常
练习题 : 求数字阶乘(递归解法版)
需求:接收用户输入的数字,计算该数字的阶乘结果
已知:负数不可以有阶乘,0的阶乘结果是1,
5 ! = 5 x 4 x 3 x 2 x 1
package cn.cxy.design;
public class TestRecursion {
public static void main(String[] args) {
int result = f(15);
System.out.println(result);
}
public static int f(int n) {
if(n == 1) {
return 1;
}else {
return n*f(n-1);
}
}
}
练习题 : 递归求目录总大小
需求:递归求目录的总大小 D:\ready,步骤分析如下: 1.列出文件夹中的所有资源–listFiles()–>File[] 2.判断,当前资源是文件还是文件夹–文件夹大小为0,文件大小需要累加 –是文件,求文件的字节量大小length(),累加就行 –是文件夹,继续列出文件夹下的所有资源–listFiles()–>File[] –判断,是文件,求文件的字节量大小length(),累加就行 –判断,是文件夹,再一次列出文件夹下的所有资源 –…重复操作 也就是说,规律就是:只要是文件夹,就需要重复步骤1 2
package cn.cxy.file;
import java.io.File;
public class FileSumRecursion {
public static void main(String[] args) {
File file = new File("D:\\ready");
long total = size(file);
System.out.println("文件夹的总大小为:"+total);
}
private static long size(File file) {
File[] fs = file.listFiles();
long sum = 0;
for(int i = 0;i < fs.length ; i++) {
File f = fs[i];
if(f.isFile()) {
sum += f.length();
}else if(f.isDirectory()) {
sum += size(f);
}
}
return sum;
}
}
练习题 : 递归删除文件夹
需求:递归删除文件夹 D:\ready\a 1.列出文件夹下的所有资源listFiles() 2.判断,当前资源是文件还是文件夹 –判断,是文件,直接删除delete() –判断,是文件夹,继续重复操作1 2 具体思路可以分为这么几步: 1.首先,我们需要指定一个根目录作为要删除的对象 2.列出文件夹下的所有资源listFiles(),并进行遍历 3.判断当前的资源,如果是文件,直接删除;如果是文件夹,则执行步骤2 4.将文件夹中的内容删除完毕后,删除文件夹本身
package cn.tedu.file;
import java.io.File;
public class TestFileDeleteRecursion {
public static void main(String[] args) {
File file = new File("D:\\ready\\a");
boolean result = del(file);
System.out.println("删除的结果为:"+result);
}
public static boolean del(File file) {
File[] fs = file.listFiles();
for (int i = 0; i < fs.length; i++) {
File f = fs[i];
if(f.isFile()) {
f.delete();
System.out.println(file.getName()+"文件删除成功!");
}else if(f.isDirectory()) {
del(f);
}
}
file.delete();
System.out.println(file.getName()+"文件夹删除成功!");
return true;
}
}
12.内部类有哪些种类?又有哪些使用场景?
-
定义在一个类中或者方法中的类称为内部类。 -
内部类可以分为以下四类:
-
成员内部类【这里是非静态的】 这是最普通的内部类,它定义在一个类的内部中,就如同一个类里的成员变量一样。 成员内部类可以无条件的访问外部类的成员属性和成员方法(包括 private 和 static 类型的成员) 这是因为在成员内部类中,隐式地持有了外部类的引用。 使用场景: 当类 A 需要使用类 B ,同时 B 需要访问 A 的成员/方法时,可以将 B 作为 A 的成员内部类。同时我们可以利用 private 内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。 -
静态内部类【这里是成员内部类被静态修饰】 这是使用 static 关键字修饰的内部类,静态内部类不持有外部类的引用,可以看作是和外部类平级的类。 我们想在静态内部类中访问外部类的成员只能 new 一个外部类的对象,否则只能访问外部类的静态属性和静态方法。 使用场景: 当类 A 需要使用类 B,而 B 不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类。 -
局部内部类 这是在代码块或者方法中创建的类。 局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方无法创建该类的对象。 我们可以把局部内部类理解为作用域很小的成员内部类。 使用场景: 局部内部类只用于当前方法或者代码块中创建、使用,属于一次性产品,使用场景比较少。 -
匿名内部类 这是一个没有名字的内部类,通常与匿名对象一起使用,实现了创建实现类+实现方法+创建对象并调用方法的功能 概括一下匿名内部类的特点: 1.必须继承一个父类或实现一个接口,并不需要增加额外的方法,只是对继承方法的实现或是覆盖。 2.只是为了获得一个对象实例,并不需要知道其实际的类型。 3.匿名内部类的匿名指的是没有类名,而不是没有引用指向它。 4.匿名内部类不能有构造方法,只能有初始化代码块。因为编译器会帮我们生成一个构造方法然后调用。 5.匿名内部类中使用到的参数是需要声明为 final 的,否则编译器会报错。 使用场景: 当我们需要实现一个接口,但不需要持有它的引用,仅需要使用一次它的某一个资源时使用
那么为什么匿名内部类中使用参数需要声明为final呢? 因为匿名内部类是创建一个对象并返回,这个对象的方法被调用的时机不确定,方法中有修改参数的可能,如果在匿名内部类中修改了参数,外部类中的参数是否需要同步修改呢?Java 为了避免这种问题,限制匿名内部类访问的变量需要使用 final 修饰,这样可以保证访问的变量不可变。 而且,除了静态内部类,剩下的成员内部类、局部内部类、匿名内部类 都会默认隐式地持有外部类的引用。
|