锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

JavaOOP 从初识到掌握

时间:2024-01-01 11:07:00 p48j5m密封连接器

请添加图片描述

文章目录

      • 第一章:对象和包装
      • 第二章 继承
      • 第三章 多态
      • 第四章 接口
      • 第五章 异常
      • 第六章 集合框架
      • 第七章 多线程
      • 第八章 File I/O

第一章:对象和包装

1.什么是类,什么是对象?

解决方案:类:具有相同特征(数据元素)和行为(功能)的对象的抽象是类。因此,对象的抽象是类,类的具体化是对象,也可以说类的例子是对象,类实际上是一种数据类型。类具有属性,是对象状态的抽象,用数据结构来描述类的属性。类具有操作性,是对象行为的抽象性,用操作名和实现操作的方法来描述。

对象:对象是人们想要研究的任何东西。它不仅能表达特定的事物,还能表达抽象的规则、计划或事件。对象有一个状态,一个对象用数据值来描述它的状态。对象还有改变对象状态的操作。对象及其操作是对象的行为。对象实现数据与操作的结合,将数据与操作包装在对象的统一中。

对象与类的关系关系:类与对象之间的关系,如模具与铸件之间的关系,类强度的结果是对象,对象的抽象是类,类描述了一组具有相同特征(属性)和相同行为的对象。

2.如何创建对象?

解:一、工厂模式

工厂模式是软件工程领域知名的设计模式,抽象了创建特定对象的过程。考虑到ECMAScript在无法创建的类别中,开发人员发明了一个函数,用函数包装具体界面创建对象的细节,如下所示:

function createPerson(name, age, job){   var o = new Object();         o.name = name;          o.age = age;          o.job = job;         o.sayName = function(){                 alert(this.name);      } return o;}var person1 = createPerson("wei",25,"software");var person2 = createPerson("bu",25,"software"); 

函数createPerson()可以根据接受的参数构建包含所有必要信息的参数Person对象。这个函数可以多次调用,每次返回一个包含三个属性和一种方法的对象。虽然工厂模式解决了创建多个相似对象的问题,但没有解决对象识别的问题,即如何知道这是哪种对象类型。

二、构建函数模式

像Array、Object这种本地结构函数在运行过程中会自动出现在执行环境中。此外,我们还可以创建自定义的结构函数,以定义自定义类型的属性和方法。例如,我们可以用结构函数重写上一个例子:

function Person(name, age, job){       this.name = name;   this.age = age;   this.job = job;   this.sayName = function(){                    alert(this.name);          }}var person1 = new Person("wei",25,"software");var person2 = new Person("bu",25,"software"); 

在这个例子中,Person()函数被替换createPerson()函数,我们注意到了Person()与createPerson()的区别在于:

  • 没有明确的创建对象
  • 直接赋值属性和方法this对象
  • 没有return语句

另外,还应该注意函数名Person使用的是大写字母P。按照惯例,构造函数应该始终从大写字母开始,而不是构造函数应该从小写字母开始。这种做法借鉴了其他方法OO语言主要是为了区别ECMAScript其他函数。由于构造函数本身也是函数,只能创建对象。

要创建一个Person必须使用实例new操作符。上述方法将通过以下四个步骤:

1.创建新对象

2.将构造函数的作用域赋予新对象(因此this指向这个新对象)

3.执行构造函数中的代码

4.返回新对象

在前面的例子的最后,person1和person2分别保存着Person一个不同的例子。这两个对象都有一个constructor(构造函数)属性指向Person。如下:

console.log(person1.constructor == Person);     //trueconsole.log(person2.constructor == Person);     //true 

对象的constructor该属性最初用于识别对象类型。然而,当涉及到检测对象的类型时,它仍然是instanceof操作符更可靠。我们在这个例子中创建的对象都是Object对象的例子,也是Person通过对象的实例,这一点instanceof可验证操作符。

console.log(person1 instanceof Object);     //trueconsole.log(person1 instanceof Person);     //trueconsole.log(person2 instanceof Object);     //trueconsole.log(person2 instanceof Person);     //true 

创建自定义的构造函数意味着他的实例可以在未来识别为特定类型;这是构造函数模式优于工厂模式的地方。在这个例子中,person1和person2之所以同是Object例子是因为所有对象都继承自己Object。

构建函数的主要问题是,每种方法都应该在实例中重新创建,造成内存浪费。在前面的例子中,person1和person都有一个名字sayName()方法,但两种方法不一样Function的实例。不要忘了ECMAScript函数也是对象,所以每定义一个函数,即实例化一个对象,从逻辑的角度来看,此时的构造函数可以定义为:

function Person(name, age, job){    this.name = name;        this.age = age;        this.job = job;        this.sayName = new Function("alert(this.name);")      逻辑上与声明函数等价} 

从这个角度来看,构造函数更容易理解每一个Person例子将包含不同的例子Function实例的本质。说清楚了,会导致域链和标识符的分析,但创建Function新实例的机制仍然相同。因此,不同实例中的同名函数是不同的,以下代码可以证实这一点。

alert(person1.sayName == person2.sayName);  //false 

然而,创建两个完成相同任务的人Function实例确实没必要;而且有this在执行代码之前,对象不需要将函数绑定到特定的对象上。因此,这个问题可以通过将函数定义转移到构造函数的外部来解决。

function Person(name, age, job){    this.name = name;        this.age = age;        this.job = job;        this.sayName = sayName;}function sayName(){   alert(this.name); } 

这解决了多个函数解决同一问题的问题,但存在新的问题。事实上,它们只在整体功能域中被某个对象调用,这使得整体对象有点不真实。更不可接受的是,如果对象需要定义许多方法,则需要定义许多整体函数,因此我们的自定义引用类型根本没有包装。幸运的是,这些问题可以通过原型模式来解决。

三、原型模式

我们创建的每个函数都有一个函数prototype(原型)属性,指向一个对象的指针,该对象的目的是包含所有特定类型的实例共享的属性和方法。使用原型对象的实例是让所有实例共享其所包含的属性和方法。换句话说,这些信息可以直接添加到原型对象中,而不是在构造函数中定义对象的实例信息,如下所示:

function Person(){}Person.prototype.name = "wei";Person.pototype.age = 27;Person.prototype.job = "Software";Person.prototype.sayName = function(){     alert(this.name);}var person1 = new Person();person1.sayName();      //"wei"var person2 = new Person();person2.sayName();      //"wei"alert(person1.sayName == person2.sayName);

​ 在此,我们将sayName()方法和所有的属性直接添加在了Person的prototype属性中,构造函数变成了空函数。即便如此,我们仍然可以通过构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但是与构造函数不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1和person2访问的都是同一组属性和同一个sayName()函数。要理解原型模式的工作原理,就必须先理解ECMAScript中原型对象的性质。

​ 原型对象的本性由于篇幅太长将会在下一章节详细分析。上面我们说了原型模式的好处,接下来我们来看一下原型模式的缺点。原型模式省略了为构造函数传递参数的这一环节,结果所有实例在默认情况下都具有相同的属性值。这会在某些程度上带来一种不便,这并不是原型模式最大的问题,因为如果我们想为一个通过原型模式创建的对象添加属性时,添加的这个属性就会屏蔽原型对象的保存的同名属性。换句话说,就是添加的这个属性会阻止我们去访问原型中的属性,但并不会改变原型中的属性。

​ 原型模式最大的问题是由其共享的本质所导致的。原型中所有的属性被很多实例共享,这种共享对函数非常合适,对包含基本值的属性也说的过去,但是对引用类型的属性值来说问题就比较突出了,下面我们来看一个例子:

function Person(){ } Person.prototype = {       constructor:Person,       name:"wei",       age:29,       friends:["乾隆","康熙"],       sayName:function(){        alert(this.name);    } }var person1 = new Person();var person2 = new Person(); person1.friends.push("嬴政");console.log(person1.friends);   //["乾隆","康熙","嬴政"]console.log(person2.friends);   //["乾隆","康熙","嬴政"]console.log(person1.friends === person2.friends);   //true

​ 上面的例子中,Person.prototype对象有一个名为friends的属性,该属性包含一个字符串数组。然后创建了两个Person的实例,接着修改person1.friends引用的数组,向数组中添加一个字符串,由于数组存在于Person.prototype中而不是person1中,所以person2.friends也会被修改。但是一般每个对象都是要有属于自己的属性的,所以我们很少看到有人单独使用原型模式来创建对象。

四、组合使用构造函数模式和原型模式

​ 创建自定义类型最常见的方式就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。下面的代码重写了前面的例子:

function Person(name, age){        this.name = name;        this.age = age;        this.friends = ["乾隆","康熙"];}Person.prototype = {        constructor:Person,        sayName:function(){       alert(this.name);    }}var person1 = new Person("wei",29);var person2 = new Person("bu",25);person1.friends.push("嬴政");console.log(person1.friends);   //["乾隆", "康熙", "嬴政"]console.log(person2.friends);   //["乾隆", "康熙"]console.log(person1.friends === person2.friends);   //falseconsole.log(person1.sayName === person2.sayName);   //true

​ 在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。所以修改了person1.friends并不会改变person2.friends,因为他们分别引用了不同的数组。

​ 这种构造函数与原型模式混成的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用的一种默认形式。

五、动态原型模式

​ 有其他OO语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常的困惑。动态原型模式就是用来解决这个问题的一个方案,它把所有的信息都封装在了构造函数中,而通过构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否要初始化原型。来看一个例子:

function Person(name, age){    this.name = name;        this.age = age;        this.friends = ["乾隆","康熙"];    //注意if语句    if(typeof this.sayName!="function"){    Person.prototype.sayName = function(){     alert(this.name);            }        }}var person1 = new Person("wei",29);person1.friends.push("嬴政"); person1.sayName();

​ 注意构造函数代码中的if语句,这里只在sayName()方法不存在的情况下才会将它添加到原型中。这断代码只有在第一次调用构造函数的时候才会被执行。此后,原型已经被初始化,不需要再做什么修改。不过要记住,这里所做的修改能立即在所有实例中得到反映。因此,这种方法可以说确实非常完美。其中if语句检查的是初始化之后应该存在的任何方法和属性–不必再用一大堆if来检查每个属性和方法,只检查其中一个即可。对于采用这样模式创建的对象,还可以使用instanceof操作符来确定他的类型。

注意:使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有的实例与新原型之间的联系。

六、寄生构造函数模式

​ 通常,在上述几种模式都不适合的情况下可以使用寄生构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象,但从表面看,这个函数又很像典型的构造函数。来看一个例子:

function Person(name, age, job){  var o = new Object();  o.name = name;  o.age = age;      o.job = job;      o.sayName = function(){  alert(this.name);    }         return o; }var person = new Person("wei",29,"banzhuan"); person.sayName();   //"wei"

​ 在这个例子中,Person函数创建了一个对象,并以相应的属性和方法初始化该对象,然后返回了这个对象。除了使用new操作符把使用的包装函数叫做构造函数之外,这个模式和工厂模式并没有多大的区别。构造函数在不返回值的情况下,会默认返回新对象的实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。

​ 这个模式可以在特殊的情况下来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式:

function SpecialArray(){   //创建数组   var values = new Array();          //添加值   values.push.apply(values,arguments);          //添加方法   values.toPipedString = function(){             return this.join("|");}       //返回数组   return values;}var colors = new SpecialArray("red","blue","green");console.log(colors.toPipedString());//red|blue|green

​ 在这个例子中,我们创建了一个名为SpecialArray的构造函数。在这个函数的内部,首先创建了一个数组,然后push()方法初始化了数组的值。随后又给数组实例添加了toPipedString()方法,用来返回以竖线分隔的数组值。最后将数组以函数的形式返回。接着,我们调用了SpecialArray构造函数,传入了初始化的值,并调用了toPipedString()方法。

​ 关于寄生构造函数模式,有一点需要声明:首先,返回的对象与构造函数或者构造函数的原型没有任何关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象的类型。由于存在这一的问题,我们建议在可以使用其他模式的情况下不要使用这种模式。

七、稳妥构造函数模式

​ 道格拉斯·克拉克福德发明了JavaScript中的稳妥对象这个概念。所谓稳妥对象,是指没有公共属性,而且其方法也不引用this对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数遵循的与寄生构造函数类似的模式,但又两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

function Person(name, age, job){       //创建要返回的新对象       var o = new Object();     //可以在这里定义私有变量和函数          //添加方法   o.sayName = function(){       alert(this.name);       };       //返回对象   return o;   }

​ 注意,在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。可以像下面使用稳妥的Person构造函数:

var person =Person("weiqi",22,"banzhuan");person.sayName();   //weiqi

​ 这样,变量person中保存的是一个稳妥对象,而除了sayName()方法外,没有别的方式可以访问其他数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得他非常适合在某些安全执行环境–例如,ADsafe(www.adsafe.org)提供的环境下使用。

​ 注意:与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间没有什么关系,因此instanceof操作符对这种对象也没有意义。

3,如何在程序中接收控制台的输入?

解:System.in 是 InputStream类型,字节流,程序使用它可读取键盘输入的数据。“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。

4,类和对象的关系?

解:类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。

5,什么是构造方法?以及构造方法的调用?

解:构造方法是一种特殊方法,它的名字必须与它所在的类的名字完全相同,而且没有类型。允许一个类中编写若干个构造方法,但必须保证他们的参数不同,即参数的个数不同,或者是参数的类型不同。实际上,在构造方法执行之前,类的内存空间已经开辟完成了。

6,什么是方法重载?

解:方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数或参数的顺序不同的方法。[1]Java的方法重载,就是在类中可以创建多个方法,它们可以有相同的名字,但必须具有不同的参数,即或者是参数的个数不同,或者是参数的类型不同。

7,static关键字 可以修饰什么,分别是什么特点?

解:a:随着类的加载而加载
b:优先于对象存在
c:被类的所有对象共享
举例:咱们班级的学生应该共用同一个班级编号
其实这个特点也是在告诉我们什么时候使用静态?
即:如果某个成员变量是被所有对象共享的,那么它就应该定义为静态的。
举例:饮水机(用静态修饰)水杯(不能用静态修饰)
即:共性用静态,特性用非静态
d:可以通过类名调用
其实它本身也可以通过对象名调用
推荐使用类名调用
静态修饰的内容一般我们称其为:
与类相关的;类成员,类方法…

8,面向对象的三大特征?什么是封装?属性封装的步骤?

解:封装,继承,多态,加get set

9,this关键字?

解:每一个行为发生的时候,肯定会有一个发生这个行为的主体。在编程语言里面每一个定义的方法里,都会有一个this关键字,这个this关键不是由在那儿定义来决定的,而是由谁来执行的决定的。这是判断this关键。


一些常见面试题

  • 什么是类,什么是对象?
  • 如何创建对象?
  • 如何在程序中接收控制台的输入?
  • 类和对象的关系?
  • 什么是构造方法?以及构造方法的调用?
  • 什么是方法重载?
  • static关键字 可以修饰什么,分别是什么特点?
  • 面向对象的三大特征?什么是封装?属性封装的步骤?

第二章 继承

1,为什么要使用继承?

解:,提高了代码的复用性,类与类之间产生了关系,java只支持单继承,不支持不多继承。

2,继承的关键字?以及继承的特点?如何使用继承?

解:类的继承是通过关键字extends来实现,类只能单一继承 子类中就是继承父类里的变量和方法 父类的构造凼数没有被继承,这里所说的意思是指在实例化子类的对象时,在new后面不能调用父类的构造凼数,但是可以在子类的构造凼数里使用super来调用父类的构造凼数,如果没有写super这句调用,子类会自动调用父类无参的构造凼数(但要确保无参构造凼数存在,否则会出错) 可以new一个子类的对象赋给一个父类的变量,当通过这个父类的变量执行方法时,其实是执行的子类的方法

3,四种访问修饰符?

解:public ,protected,default,private

4,什么是方法重写?重载和重写的区别?

解:重写是子类的方法覆盖父类的方法,要求方法名和参数都相同 重载是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,最常见的重载的例子就是类的构造函数,可以参考api帮助文档看看类的构造方法

5,super关键字的使用?

解:

  super关键和this作用类似,是被屏蔽的成员变量
或者成员方法或变为可见,或者说用来引用被屏蔽的成员
变量和成员成员方法。
不过super是用在子类中,目的是访问直接父类中被屏蔽的
成员,注意是直接父类(就是类之上最近的超类)

6,什么是抽象类?关键字?抽象类的特点?

解:

抽象类不能实例化。
2、抽象类可以包含抽象方法和抽象访问器。
3、不能用 sealed 修饰符修饰抽象类,因为这两个修饰符的含义是相反的。 采用 sealed 修饰符的类无法继承,而 abstract 修饰符要求对类进行继承。
4、从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实际实现。
扩展资料
抽象类与其他类的比较
一、与具体类比较
1、抽象类不能直接实例化,并且对抽象类使用 new 运算符会导致编译时错误。虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为 null,或者含有对非抽象类的实例的引用(此非抽象类是从抽象类派生的)。
2、允许(但不要求)抽象类包含抽象成员。
3、抽象类不能被密封。
二、与接口比较
抽象类表示该类中可能已经有一些方法的具体定义,但是接口就仅仅只能定义各个方法的界面(方法名,参数列表,返回类型),并不关心具体细节。

7,什么是抽象方法?抽象方法的特点?

解:java中的抽象方法就是以abstract修饰的方法,这种方法只声明返回的数据类型、方法名称和所需的参数,没有方法体,也就是说抽象方法只需要声明而不需要实现。抽象方法的特点:只包含方法定义,但没有具体实现的方法,需要其子类或者子类的子类来具体实现。抽象类的特点:含有一个或多个抽象方法的类称为抽象类。抽象类中可以包含非抽象方法,抽象类不能够被实例化,这是因为它包含了没有具体实现的方法,即可实例化的类一定不是抽象类,不包含未具体实现的抽象方法。

8,final关键字的使用?

解:用final修饰的类,不能被继承

用final修饰的方法不能被子类重写

用final修饰的变量(包括成员变量和局部变量)将变成常量,是能赋值一次。


一些常见面试题

  • 为什么要使用继承?
  • 继承的关键字?以及继承的特点?如何使用继承?
  • 四种访问修饰符?
  • 什么是方法重写?重载和重写的区别?
  • super关键字的使用?
  • 什么是抽象类?关键字?抽象类的特点?
  • 什么是抽象方法?抽象方法的特点?
  • final关键字的使用?

第三章 多态

1,为什么使用多态?

解: 实现代码的复用,避免代码的冗余;减少代码之间的关联性,即耦合度,方便后期对代码的修改,功能的改善,不必牵一发而动全身,减少不必要的麻烦;能够通过重写子类的方法,使不同的对像具有不同的功能,扩展了功能。

2,什么是多态?

解: 父类类型的变量指向子类创建的对象,使用该变量调用父类中一个被子类重写的方法,则父类中的方法呈现出不同的行为特征,这就是多态。简单的来说编译时方法行为表现的是父类中的方法,运行时方法行为表现的是子类中重写该方法的行为特征。

3,举例说明说明生活中的多态?

解:打印机,同一打印机,不同的材料出来不同的东西

4,如何实现多态?以及使用多态的方式?

解:上转型对象实现多态上转型对象与下转型对象 上转型对象:子类创建的对象赋值给父类声明变量,则该对象称为上转型对象,这个过程称为对象上转型,对应于数据类型转换中的自动类型转换;下转型对象:上转型对象再强制转换为创建该对象的子类类型的对象,即将上转型对象还原为子类对象,对应于数据类型转换中的强制类型转换。

5,什么是向上转型,向下转型?

解:向上转型就是子类转为父类,向下转型是父类转子类

6,使用的多态有什么好处?

解:

多态有编译时多态 和运行时多态。
第一个是通过方法重载实现;第二个是通过方法覆盖实现(子类覆盖父类方法)。
第一种就是我们调用方法是不用区分参数类型,程序会自动执行相应方法,如: 加法运算,可以使int相加,可以是double相加,都是同一个方法名。
第二种就是动态绑定,使用父类引用指向子类对象,再调用某一父类中的方法时,不同子类会表现出不同结果。 这样的作用就是扩展性极好,玩过网游的话应该知道 游戏中有不同的角色,它们都有一个父类,它们做相同动作时表现出来的效果就会不一样,比如跑,魔法师的跑跟战士的跑就不会一样,这就是俩者都覆盖了父类中的跑方法,各自有自己的现实,表现出来多态。  如果有一天你想再加个角色,只用再写一个类继承该父类,覆盖其中的跑方法就行了,其他代码不用怎么改,所以可维护性也很好。

7,instanceof关键字的使用?

解:严格来说 instanceof 是Java 中的一个双目运算符,由于它是由字母组成的,所以也是 Java 的保留关键字


一些常见面试题

  • 为什么使用多态?
  • 什么是多态?
  • 举例说明说明生活中的多态?
  • 如何实现多态?以及使用多态的方式?
  • 什么是向上转型,向下转型?
  • 使用的多态有什么好处?
  • instanceof关键字的使用?

第四章 接口

1,简述抽象类和接口的应用场合

解: 首先,Java 允许单继承多接口。从上面可以推断出接口是比继承更灵活的方式,因为类允许无限的接口实现。所以有可能尽量用接口。但实际会出现一系列类在实现接口的时候也许有共同的某几个方法的实现,但另外一部分实现不同。此时用抽象类可以实现一部分共同方法,并将其他留到具体类实现。

2,面向接口编程的好处是什么?

解:在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

3,如何使用接口

解:接口的定义:public interface usb{}关键字 interface;

4,接口的特点?

解:1.接口使用interface修饰

2.接口是常量和抽象方法的集合

常量: 默认使用 public static final

方法: 默认使用 public abstract修饰

3.在JDK1.8以后不仅仅只有常量和抽象方法,还有默认方法和静态方法

默认方法用来直接给子类使用,如果子类想重写也可以自行根据需求重写,不会强制重写

静态方法用来直接通过接口名访问

4.接口不能够实例化

5.接口如果想要实例化–利用多态

6.接口的实现类特点:

a.如果一个类想要实现一个接口就必须实现接口中定义的所有抽象方法

b.如果一个类不想实现接口中的抽象方法,那么实现类也升级为接口

7.接口是一种规范

8.接口可以用来扩展功能

5,接口和抽象类的区别?

解:抽象类要被子类继承,接口要被类实现。

接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现。

接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。


一些常见面试题

  • 简述抽象类和接口的应用场合
  • 面向接口编程的好处是什么?
  • 如何使用接口
  • 接口的特点?
  • 接口和抽象类的区别?

第五章 异常

1,什么是异常?

解:从管理的角度来看,什么是异常?当过程中有了特殊原因引起的变异的时候,过程就发生了异常。反之,当过程中只有普通原因引起变异的时候,过程是正常的。 2、过程也就是系统。所谓过程中普通原因就是系统中固有的因素;所谓过程中的特殊原因就是系统外的因素或者系统中固有因素产生变质,超出了系统对它的要求,从而成为不正常因素。 3、正常和异常是相对的,不是绝对的。判定正常与否时采用的原则是统计学正常分布中3 个标准差(3西格玛)原则。 4、最常用来判定普通原因和特殊原因的工具是控制图。当控制图中出现位于控制极限外面的数据点,或者,虽然数据点没有超出控制极限,但是,数据点和点之间出现了非随机的现象,也就应当判定存在特殊原因可能。 5、特殊原因通常是不好的,但也有可能是好的。不好的特殊原因,要设法加以确定,然后,消除它;好的特殊原因也应当确定下来,设法加以标准化,巩固好的成绩。

2,异常的体系结构?

解: 异常是程序运行过程中出现的错误。Java语言的异常处理框架,是Java语言健壮性的一个重要体现。 Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。 在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。 Java异常体系结构呈树状。

3,Java中,如何进行异常处理?

解:

throws捕获并向外抛出异常
throw抛出异常
try catch是内部捕获异常并做自定义处理
finally是无论是否有异常都会被处理的语句,除非在finally前存在被执行的System.exit(int i)时除外

4,说出5个常见的运行时异常

解:ArgumentNullException //参数异常
ArgumentOutOfRangeException //参数异常
DivideByZeroException //除数为0异常
IndexOutOfRangeException //索引超出范围的异常
NullReference-Exception//参数异常

5,throw与throws的区别是什么?

解:throw用在方法体内,上面代码显示了,是直接在main方法体内,throw,如果执行了,那么一定是抛出了某种异常了,安生throws表示可能出现,但不一定。

6,try-catch块中存在return语句,是否还执行finally块,如果执行,说出执行顺序

解:它会先执行finally快,在执行return语句。

7,try-catch- finally块中, finally块唯一不执行的情况是什么?

解:就是在运行finally之前把程序关闭

8,try-catch-finally的组合方式,以及执行情况?

解:从上到下

9,使用log4j记录日志的步骤?

解:Spring Boot配置 log4j记录日志 我们在老的项目框架ssh,springMVC中都使用过log4j日志模块。

10,日志记录器输出级别

解: 级别 日志 函数 描述 级别高低(从高到低) DEBUG logging.debug() 最低级别


一些常见面试题

  • 什么是异常?
  • 异常的体系结构?
  • Java中,如何进行异常处理?
  • 说出5个常见的运行时异常
  • throw与throws的区别是什么?
  • try-catch块中存在return语句,是否还执行finally块,如果执行,说出执行顺序
  • try-catch- finally块中, finally块唯一不执行的情况是什么?
  • try-catch-finally的组合方式,以及执行情况?
  • 使用log4j记录日志的步骤?
  • 日志记录器输出级别

第六章 集合框架

1,集合框架的体系结构?

解:java集合框架其实就是一些能提供更有效的组织和操作数据的数据结构
2、在面向对象思想里,一种数据结构就是一个容器
3、java集合框架支持两个类型容器:(1)第一种是存储一个元素集合,称为集合;
(2)第二种是存储键/值对,称为图。
4、java结合框架支持三种集合:规则集(Set)、线性表(List)和队列(Queue)。
(1)Set的实例用于存储一组不重复的元素,
(2)List的实例用于存储一个由元素构成的有序集合,
(3)Queue的实例用于存储用先进先出方式处理的对象
5、在java集合框架中定义的所有接口和类都存储在java.util包中
6、java结合框架中的所有具体类都实现了java.lang.Cloneable和java.io.Serializable接口,所以,它们的实例都是可复制且可序列化的

2,List、Map是否都继承自Collection接口 ?

解:Collection是最基本的集合接口,声明了适用于JAVA集合(只包括Set和List)的通用方法。 Set 和List 都继承了Conllection;Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只 是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责) Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

3,和数组采用相同存储结构的集合类型是什么?

解:java.util.ArrayList和java.util.Vector.都是采用数组形式来实现的。

4,Iterator接口的作用是什么?

解:

迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
从定义可见,迭代器模式是为容器而生。很明显,对容器对象的访问必然涉及到遍历算法。你可以一股脑的将遍历方法塞到容器对象中去;或者根本不去提供什么遍历算法,让使用容器的人自己去实现去吧。这两种情况好像都能够解决问题。
然而在前一种情况,容器承受了过多的功能,它不仅要负责自己“容器”内的元素维护(添加、删除等等),而且还要提供遍历自身的接口;而且由于遍历状态保存的问题,不能对同一个容器对象同时进行多个遍历。第二种方式倒是省事,却又将容器的内部细节暴露无遗。

5,ArrayList集合类的方法?

解:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变

·ArrayList集合的特点

底层是数组实现的,长度可以变化

·泛型的使用

用于约束集合中存储元素的数据类型

6,ArrayList集合类的底层实现方式?

解:继承Collection和Iterable接口
有序的允许重复的集合,允许null值(不推荐),允许将自身作为元素(不推荐)
此接口和实现子类可以对列表中的每个元素的插入位置进行精确控制。可以根据索引访问元素,也可以搜索列表中的元素。
有iterator迭代器可单向遍历,同时有ListIterator双向遍历
选用概述
线程安全用Vector
线程不安全,查询多用ArrayList,增删多用LinkedList

  • ArrayList:底层数组,查询快,增删慢,线程不安全
  • LinkedList:底层双向链表,查询慢,增删快,线程不安全
  • Vector:底层数组,线程安全
    方法
    继承Collection的方法
    方法一般都会多出用索引的重载方法
    get(int index):通过索引取出元素
    subList(int start,int end): 开始到结束-1的位置,获取此段的子list的视图
    在此子List上的修改会使原List也被修改

7,LinkedList集合类的常用方法?

解:public void addFirst(E e):在列表头添加元素
public void addLast(E e):在列表尾添加元素
public void push(E e):入栈:从列表头入栈
public E pop(E e):出栈:从列表头出栈
public E getFirst():获取列表第一个元素
public E getLast():获取列表最后一个元素
public E removeFirst():移除列表第一个元素并返回被移除元素
public boolean isEmpty():判断列表是否为空,如为空返回tru

8,LinkedList集合类的底层实现方式

解:LinkedList类是List接口的实现类,它是一个集合,可以根据索引来随机的访问集合中的元素,还实现了Deque接口,它还是一个队列,可以被当成双端队列来使用。虽然LinkedList是一个List集合,但是它的实现方式和ArrayList是完全不同的,ArrayList的底层是通过一个动态的Object[]数组来实现的,而LinkedList的底层是通过链表来实现的,因此它的随机访问速度是比较差的,但是它的删除,插入操作会很快。

9,ArryList和LinkedList的区别和各自优点?

解:

ArrayList:
其实是包装了一个数组 ,当实例化一个ArrayList时,一个数组也被实例化,当向ArrayList中添加对象是,数组的大小也相应的改变。这样就带来以下有缺点:

快速随即访问 你可以随即访问每个元素而不用考虑性能问题,通过调用get(i)方法来访问下标为i的数组元素。

向其中添加对象速度慢 当你创建数组是并不能确定其容量,所以当改变这个数组时就必须在内存中做很多事情。

操作其中对象的速度慢 当你要想数组中任意两个元素中间添加对象时,数组需要移动所有后面的对象。

LinkedList:

LinkedList是通过节点直接彼此连接来实现的。每一个节点都包含前一个节点的引用,后一个节点的引用和节点存储的值。当一个新节点插入时,只需要修改其中保持先后关系的节点的引用即可,当删除记录时也一样。这样就带来以下有缺点:

操作其中对象的速度快 只需要改变连接,新的节点可以在内存中的任何地方。不能随即访问, 虽然存在方法,但是这个方法是通过遍历接点来定位的所以速度慢。

10,Map集合的特点?

解:Map 集合的特点: (1) map 集合一次要存储两个元素, 第一个是 key(键-索引), 第二个内容是 value(值)

11,Map集合的常用方法?

解:1 添加,删除操作:Object put(Object key, Object value): 向集合中加入元素Object remove(Object key):

12,迭代器Iterator的使用方式?

解:Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊。Collection接口中有一个方法,叫iterator(),这个方法返回的就是迭代器的实现类对象。

13,增强for循环的语法

解:Java5 引入了一种主要用于数组的增强型 for 循环。

Java 增强 for 循环语法格式如下:

for(声明语句 : 表达式){ //代码句子}

**声明语句:**声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。

**表达式:**表达式是要访问的数组名,或者是返回值为数组的方法。

14,什么是泛型集合,为什么要使用泛型集合?

解:我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int类型数据,另一个处理String类型数据,或者其

他自定义类型数据,但是我们没有办法,只能分别写多个方法处理每种数据类型,因为方法的参数类型不同。那有一种方法,

在方法中传入通用的数据类型,就可以用来合并代码,这就是泛型。

为什么要使用泛型

为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能出来int数据类型:


一些常见面试题

  • 集合框架的体系结构?
  • List、Map是否都继承自Collection接口 ?
  • 和数组采用相同存储结构的集合类型是什么?
  • Iterator接口的作用是什么?
  • ArrayList集合类的方法?
  • ArrayList集合类的底层实现方式?
  • LinkedList集合类的常用方法?
  • LinkedList集合类的底层实现方式
  • ArryList和LinkedList的区别和各自优点?
  • Map集合的特点?
  • Map集合的常用方法?
  • 迭代器Iterator的使用方式?
  • 增强for循环的语法
  • 什么是泛型集合,为什么要使用泛型集合?

第七章 多线程

1,说明进程和线程的区别

解:线程在进程下行进(单纯的车厢无法运行)一个进程可以包含多个线程(一辆火车可以有多个车厢)不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)进程使用的内存地址可以上,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

2,创建线程的方式有哪两种?

解: 1是通过继承线程java.lang.thread定义线程 1是通过实现java.lang.Runnable接口来定义线程

3,线程的状态有哪几个?

解: 就绪:线程分配了CPU以外的全部资源,等待获得CPU调度执行:线程获得CPU,正在执行阻塞:线程由于发生I/O或者其他的操作导致无法继续执行,就放弃处理机,转入线程就绪队列第四种:挂起:由于终端请求,

4,线程的什么方法可以设置线程休眠、线程的强制执行、线程的礼让?

解:一.线程的强制执行
线程的强制执行是指当满足于某些条件之后,某一个线程对象可以一直独占资源,一直到该线程程序执行结束。如果主线程和子线程交替执行,某个时间段需要主线程执行完毕再执行子线程,可以使用Thread类里面的join方法:

需要注意的是在线程强制执行的时候一定要获取强制执行线程对象之后才可以执行join()

二.线程的礼让
线程的礼让指的是先将资源让出去给别的线程先执行,线程的礼让可以使用Thread中提供的方法:

礼让执行的时候每一次调用yield()方法都只会礼让一次当前的资源。

三.线程优先级
线程的优先级越高越有可能抢占到资源,Thread里面有两个处理方法:
(1)设置优先级:

(2)获取优先级:

在进行优先级定义的时候都是通过int型的数字来完成的,而对于此数字的选择在Thread类里面有三个常量:

最高优先级:MAX_PRIORITY , 10
中等优先级:NORM_PRIORITY ,5
最低优先级:MIN_PRIORITY ,1
那么主方法是一个主线程,主线程的优先级为:5
主线程属于中等优先级,而默认创建的线程也是中等优先级。
需要注意的是优先级高的有可能先执行,而不是一定会先执行。

5,什么情况下需要进行线程的同步,线程同步有几种方式?

解:

线程有时候回和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。这时候,我们就需要引入线程“同步”机制,即各位线程之间要有顺序使用,不能杂乱无章随意使用。
线程同步的方法
1、wait():使一个线程处于等待状态,并且释放所持有的对象的lock。



2、sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。



3、notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。



4、notityAll ():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。




扩展资料:

在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

6,如何启动线程?

解:

public class PrintThread extends Thread {
public void run() {
    System.out.println("我是线程!继承自Thread");
}
public static void main(String args[]) {
    (new PrintThread()).start();
}

} `

7,线程对象调用start()方法和调用run()方法的区别?

解:run方法是线程的主体,你都说了,但这并不是启动线程 应该是调用线程的start() 方法才是启动线程 Thread类中run()和start()方法的区别如下: run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用; start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;

8,什么是线程优先级,它在线程调度中的作用?

解: 每一个线程都是有优先级的,一般来说,高优先级线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OSdependent)。可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。

9,join()方法和yield()方法的区别是什么?

解:

yield:理论上,yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。注意:它是一个静态的原生(native)方法;它告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程;
它不能保证使得当前正在运行的线程迅速转换到可运行的状态;它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态。
join:线程实例的方法join()方法可以使得一个线程在另一个线程结束后再执行。如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行。
package test;
public class YieldExample {
   public static void main(String[] args) {
      Thread producer = new Producer();
      Thread consumer = new Consumer();
      producer.setPriority(Thread.MIN_PRIORITY); 
      consumer.setPriority(Thread.MAX_PRIORITY); 
      producer.start();
      consumer.start();
   }
}
class Producer extends Thread{
   public void run()   {
      for (int i = 0; i < 5; i++)     {
         System.out.println("I am Producer : Produced Item " + i);
         Thread.yield();
      }
   }
} 
class Consumer extends Thread{
   public void run()   {
      for (int i = 0; i < 5; i++)      {
         System.out.println("I am Consumer : Consumed Item " + i);
         Thread.yield();
      }
   }
}

package test;
public class JoinExample{
   public static void main(String[] args) throws InterruptedException   {
      Thread t = new Thread(new Runnable()         {
            public void run()            {
               System.out.println("First task started");
               System.out.println("Sleeping for 2 seconds");
               try
               {
                  Thread.sleep(2000);
               } catch (InterruptedException e)     {
                  e.printStackTrace();
               }
               System.out.println("First task completed");
            }
         });
      Thread t1 = new Thread(new Runnable()     {
            public void run()       {
               System.out.println("Second task completed");
            }
         });
      t.start(); 
      t.join(); 
      t1.start();
   }
 }

10,多个并发线程访问同一资源的同步代码块时的特点?

解:同步代码块

1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块,其语法如下:

synchronized(obj){

//同步代码块

}

其中obj就是同步监视器,它的含义是:线程开始执行同步代码块之前,必须先获得对同步代码块的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。虽然java程序允许使用任何对象作为同步监视器,但是同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

2.小例子

Account.java

public class Account { 
        	private String accountNo ; 	private double balance;	public String getAccountNo() { 
        		return accountNo;	}	public void setAccountNo(String accountNo) { 
        		this.accountNo = accountNo;	}	public double getBalance() { 
        		return balance;	}	public void setBalance(double balance) { 
        		this.balance = balance;	}	public Account(String accountNo, double balance)	{ 
        		super();		this.accountNo = accountNo;		this.balance = balance;	}	}

DrawThread.java

public class DrawThread extends Thread { 
        	private Account account;	private double drawAmount;		public DrawThread(String name , Account account, double drawAmount)	{ 
        		super(name);		this.account = account;		this.drawAmount = drawAmount;	} 	@Override	public void run() { 
        		synchronized (account)		{ 
        			if(account.getBalance() >= drawAmount){ 
        				System.out.println(getName()+"取钱成功,吐出钞票:"+ drawAmount);				try				{ 
        					Thread.sleep(1);				} catch (InterruptedException e)				{ 
        					e.printStackTrace();				
锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章