JavaOOP 从初识到掌握

请添加图片描述

第一章:对象和封装

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和person2都有一个名为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.prototype.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();				}				account.setBalance(account.getBalance()-drawAmount);				System.out.println("\t余额为:"+account.getBalance());			}else{				System.out.println(getName()+"取钱失败,余额不足");			}		}	}}

DrawTest.java

public class DrawTest { 	public static void main(String[] args) {		Account acct = new Account("12345",1000);		new DrawThread("甲", acct, 600).start();		new DrawThread("乙", acct, 600).start();;			}}

运行出现的结果:

甲取钱成功,吐出钞票:600.0

余额为:400.0

乙取钱失败,余额不足

3.如果将DrawThread的同步去掉:

	public void run() {//		synchronized (account)//		{			if(account.getBalance() >= drawAmount){				System.out.println(getName()+"取钱成功,吐出钞票:"+ drawAmount);				try				{					Thread.sleep(1);				} catch (InterruptedException e)				{					e.printStackTrace();				}				account.setBalance(account.getBalance()-drawAmount);				System.out.println("\t余额为:"+account.getBalance());			}else{				System.out.println(getName()+"取钱失败,余额不足");			}//		}	}

会出现的情况有三种:

第一种:

甲取钱成功,吐出钞票:600.0

乙取钱成功,吐出钞票:600.0

余额为:-200.0

余额为:-200.0

第二种:

乙取钱成功,吐出钞票:600.0

甲取钱成功,吐出钞票:600.0

余额为:400.0

余额为:-200.0

第三种:

甲取钱成功,吐出钞票:600.0

乙取钱成功,吐出钞票:600.0

余额为:400.0

余额为:400.0

程序使用synchronized将run()方法里的方法修改成同步代码块,同步监视器就是account对象,这样的做法符合“加锁-修改-释放锁”的逻辑,这样就可以保证并发线程在任一时刻只有一个线程进入修改共享资源的代码区。多次运行,结果只有一个。

同步方法

该类的对象可以方便被多个线程安全的访问;

每个线程调用该对象的任意方法之后都得到正确的结果;

每个线程调用该对象的任意方法之后;该对象状态依然能保持合理状态。

2.不可变类总是线程安全的,因为它的对象状态不可改变可变类需要额外的方法来保证其线程安全,在Account类中我们只需要balance的方法变成同步方法即可。

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;	}		//提供一个线程安全的draw()方法完成取钱的操作	public synchronized void draw(double drawAmount)	{		if(balance>=drawAmount){            System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);            try{                Thread.sleep(1);            }catch (InterruptedException ex){                ex.printStackTrace();            }            balance-=drawAmount;            System.out.println("\t余额为:"+balance);        }else{            System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");        }	}}

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() {		account.draw(drawAmount);	}	}

DrawTest.java

public class DrawTest { 	public static void main(String[] args) {		Account acct = new Account("12345",1000);		new DrawThread("甲", acct, 600).start();		new DrawThread("乙", acct, 600).start();;			}}

注意,synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等。在Account类中定义draw()方法,而不是直接在run()方法中实现取钱逻辑,这种做法更符合面向对象规则。DDD设计方式,即Domain Driven Design(领域驱动设计),认为每个类都应该是完备的领域对象,Account代表用户账户,就应该提供用户的相关方法,通过draw()方法执行取钱操作,而不是直接将setBalance方法暴露出来任人操作。

但是可变类的线程安全是以减低程序的运行效率为代价,不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(共享资源)的方法进行同步。同时可变类有两种运行环境:单线程环境和多线程环境,则应该为可变类提供两个版本,即线程安全版本和线程不安全版本。如jdk提供的StringBuilder在单线程环境下保证更好的性能,StringBuffer可以保证多线程安全。

释放同步监视器的锁定

1.任何线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,那么如何释放对同步监视器的锁定呢,线程会在以下几种情况下释放同步监视器:

1)当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器;

2)当前线程在同步代码块、同步方法中遇到break,return终止了该代码块、方法的继续执行;

3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、方法的异常结束;

4)当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

2.以下几种情况,线程不会释放同步监视器:

1)线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;

2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器,当然,程序应尽量避免使用suspend()和resume()方法来控制线程。

同步锁

1.java提供了一种功能更为强大的线程同步机制,通过显示定义同步锁对象来实现同步,这里的同步锁由Lock对象充当。

Lock对象提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock是控制多个线程对资源共享进行访问的工具,通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。

某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock,ReadWriteLock是Java5提供的两个接口,并为Lock提供了Reentrant实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。在Java8中提供了新型的StampLock类,在大多数场景下它可以代替传统的ReentrantReadWriteLock。ReentrantReadWriteLock为读写操作提供了三种锁模式:Writing,ReadingOptimistic,Reading.

2.在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。主要的代码格式如下:

public class X {	//定义锁对象	private final ReentrantLock lock = new ReentrantLock();	//定义需要保护线程安全的方法	public void m(){		//加锁		lock.lock();		try		{			//method body		} catch (Exception e)		{			e.getStackTrace();		}		finally {			lock.unlock();		}	}}

将Account.java修改为:

public class Account {	private final ReentrantLock lock = new ReentrantLock();	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;	}		public void draw(double drawAmount)	{		lock.lock();		try		{			if(balance>=drawAmount){	            System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);	            try{	                Thread.sleep(1);	            }catch (InterruptedException ex){	                ex.printStackTrace();	            }	            balance-=drawAmount;	            System.out.println("\t余额为:"+balance);	        }else{	            System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");	        }		} finally {			lock.unlock();		}	}}

使用Lock与使用同步代码块有点类似,只是使用Lock时可以显示使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器。使用Lock时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象,同一时刻只能有一个线程进入临界区。Lock提供了同步方法和同步代码块所没有的其他功能,包括使用非块状结构的tryLock()方法,以及试图获得可中断锁的lockInterruptibly()方法,还有获取超时失效锁的tryLock(long,TimeUnit)方法。

死锁

当两个线程互相等待对方释放同步监视器就会发生死锁,Java虚拟机没有检测,也没有采取措施来处理死锁的情况,所以多线程程序应该采取措施避免死锁出现,一旦出现死锁,程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。

如DeadLock.java所示:

A.java

public class A {	public synchronized void foo(B b){        System.out.println("当前线程名为:"+Thread.currentThread().getName()+"进入了A实例的foo()方法");        try{            Thread.sleep(200);        }catch(InterruptedException ex){            ex.printStackTrace();        }        System.out.println("当前线程名为:"+Thread.currentThread().getName()+"试图调用B实例的last()方法");        b.last();    }    public synchronized void last(){        System.out.println("进入了A类的last()方法内部");    }}

B.java

public class B {	public synchronized void bar(A a){        System.out.println("当前线程名为:"+Thread.currentThread().getName()+"进入了B实例的bar()方法");        try{            Thread.sleep(200);        }catch(InterruptedException ex){            ex.printStackTrace();        }        System.out.println("当前线程名为:"+Thread.currentThread().getName()+"试图调用A实例的last()方法");        a.last();    }    public synchronized void last(){        System.out.println("进入了B类的last()方法内部");    }}

DeadLock.java

public class DeadLock implements Runnable {	A a = new A();	B b = new B(); 	public static void main(String[] args) {		DeadLock dLock  = new DeadLock();		new Thread(dLock).start();		dLock.init();	}		public void init(){		Thread.currentThread().setName("主线程");		a.foo(b);		System.out.println("进入了主线程之后...");	}		public void run() {		Thread.currentThread().setName("副线程");		b.bar(a);		System.out.println("进入了副线程之后...");	}	}

结果有:(四种情况之一)

当前线程名为:副线程进入了B实例的bar()方法

当前线程名为:主线程进入了A实例的foo()方法

当前线程名为:主线程试图调用B实例的last()方法

当前线程名为:副线程试图调用A实例的last()方法


11,常见的线程安全和非线程安全类型?

解:线程安全(Thread-safe)的集合对象:

Vector 线程安全:
HashTable 线程安全:
StringBuffer 线程安全:
Properties线程安全
enumeration
statck

非线程安全的集合对象:

ArrayList :
LinkedList:
HashMap:
HashSet:
TreeMap:
TreeSet:
StringBulider:


一些常见面试题

  • 说明进程和线程的区别
  • 创建线程的方式有哪两种?
  • 线程的状态有哪几个?
  • 线程的什么方法可以设置线程休眠、线程的强制执行、线程的礼让?
  • 什么情况下需要进行线程的同步,线程同步有几种方式?
  • 如何启动线程?
  • 线程对象调用start()方法和调用run()方法的区别?
  • 什么是线程优先级,它在线程调度中的作用?
  • join()方法和yield()方法的区别是什么?
  • 多个并发线程访问同一资源的同步代码块时的特点?
  • 常见的线程安全和非线程安全类型?

第八章 File I/O

1,File类的常用方法?

解:

给你一个中文的吧。你不想看英文的API;File类是直接继承Object类的。Object类的方法我就不不写了:
字段摘要 
static String pathSeparator 
          与系统有关的路径分隔符,为了方便,它被表示为一个字符串。 
static char pathSeparatorChar 
          与系统有关的路径分隔符。 
static String separator 
          与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。 
static char separatorChar 
          与系统有关的默认名称分隔符。 
  构造方法摘要 
File(File parent, String child) 
          根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 
File(String pathname) 
          通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 
File(String parent, String child) 
          根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 
File(URI uri) 
          通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。 
  方法摘要 
 boolean canExecute() 
          测试应用程序是否可以执行此抽象路径名表示的文件。 
 boolean canRead() 
          测试应用程序是否可以读取此抽象路径名表示的文件。 
 boolean canWrite() 
          测试应用程序是否可以修改此抽象路径名表示的文件。 
 int compareTo(File pathname) 
          按字母顺序比较两个抽象路径名。 
 boolean createNewFile() 
          当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 
static File createTempFile(String prefix, String suffix) 
          在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。 
static File createTempFile(String prefix, String suffix, File directory) 
           在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。 
 boolean delete() 
          删除此抽象路径名表示的文件或目录。 
 void deleteOnExit() 
          在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 
 boolean equals(Object obj) 
          测试此抽象路径名与给定对象是否相等。 
 boolean exists() 
          测试此抽象路径名表示的文件或目录是否存在。 
 File getAbsoluteFile() 
          返回此抽象路径名的绝对路径名形式。 
 String getAbsolutePath() 
          返回此抽象路径名的绝对路径名字符串。 
 File getCanonicalFile() 
          返回此抽象路径名的规范形式。 
 String getCanonicalPath() 
          返回此抽象路径名的规范路径名字符串。 
 long getFreeSpace() 
          返回此抽象路径名指定的分区中未分配的字节数。 
 String getName() 
          返回由此抽象路径名表示的文件或目录的名称。 
 String getParent() 
          返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。 
 File getParentFile() 
          返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。 
 String getPath() 
          将此抽象路径名转换为一个路径名字符串。 
 long getTotalSpace() 
          返回此抽象路径名指定的分区大小。 
 long getUsableSpace() 
          返回此抽象路径名指定的分区上可用于此虚拟机的字节数。 
 int hashCode() 
          计算此抽象路径名的哈希码。 
 boolean isAbsolute() 
          测试此抽象路径名是否为绝对路径名。 
 boolean isDirectory() 
          测试此抽象路径名表示的文件是否是一个目录。 
 boolean isFile() 
          测试此抽象路径名表示的文件是否是一个标准文件。 
 boolean isHidden() 
          测试此抽象路径名指定的文件是否是一个隐藏文件。 
 long lastModified() 
          返回此抽象路径名表示的文件最后一次被修改的时间。 
 long length() 
          返回由此抽象路径名表示的文件的长度。 
 String[] list() 
          返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 
 String[] list(FilenameFilter filter) 
          返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。 
 File[] listFiles() 
          返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 
 File[] listFiles(FileFilter filter) 
          返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 
 File[] listFiles(FilenameFilter filter) 
          返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 
static File[] listRoots() 
          列出可用的文件系统根。 
 boolean mkdir() 
          创建此抽象路径名指定的目录。 
 boolean mkdirs() 
          创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 
 boolean renameTo(File dest) 
          重新命名此抽象路径名表示的文件。 
 boolean setExecutable(boolean executable) 
          设置此抽象路径名所有者执行权限的一个便捷方法。 
 boolean setExecutable(boolean executable, boolean ownerOnly) 
          设置此抽象路径名的所有者或所有用户的执行权限。 
 boolean setLastModified(long time) 
          设置此抽象路径名指定的文件或目录的最后一次修改时间。 
 boolean setReadable(boolean readable) 
          设置此抽象路径名所有者读权限的一个便捷方法。 
 boolean setReadable(boolean readable, boolean ownerOnly) 
          设置此抽象路径名的所有者或所有用户的读权限。 
 boolean setReadOnly() 
          标记此抽象路径名指定的文件或目录,从而只能对其进行读操作。 
 boolean setWritable(boolean writable) 
          设置此抽象路径名所有者写权限的一个便捷方法。 
 boolean setWritable(boolean writable, boolean ownerOnly) 
          设置此抽象路径名的所有者或所有用户的写权限。 
 String toString() 
          返回此抽象路径名的路径名字符串。 
 URI toURI() 
          构造一个表示此抽象路径名的 file: URI。 
 URL toURL() 
          已过时。 此方法不会自动转义 URL 中的非法字符。建议新的代码使用以下方式将抽象路径名转换为 URL:首先通过 toURI 方法将其转换为 URI,然后通过 URI.toURL 方法将 URI 装换为 URL。

2,什么是方法迭代?

解:迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。

对计算机特定程序中需要反复执行的子程序*(一组指令),进行一次重复,即重复执行程序中的循环,直到满足某条件为止,亦称为迭代。

3,什么是流?

解: IO流就相当与我们日常生活中的管道,我们通过管道来把水引到用户,通过管道把石油输送到大罐.同样,我们利用流来从硬盘的文件中读数据到你的程序中,利用流来写数据到硬盘的文件 文件流 缓冲流 数据流 转换流 Print流 Object流正是为了实现这些功能的不同的类,他们具体包含了实现这些功能的方法

4,流的分类?

按方向分:输入流,输出流

(注意,是站在程序的角度来看方向),输入流用于读文件,输出流用于写文件

按读取的单位分:字节流,字符流

按处理的方式分:节点流,处理流

比如,FileInputStream和BufferedInputStream(后者带有缓存区功能-byte[])

IO流的4大基类:InputStream,OutputStream,Reader,Writer

5,使用字节流读取文件的步骤?

解:Java.io 包提供了大量的流类,抽象类有四种:InputStream、OutputStream、Reader 和 Writer。称 InputStream 类及其子类对象为字节输入流类字节输出流类字符输入流类字符输出流类
InputStream 类的常用方法:

  • voidwrite(int n):输出流调用该方法向输出流写入单个字节。
  • void write(byte b[]):输出流调用该方法向输出流写入一个字节数组。
  • void write(byte b[],int off,int len):从给定字节数组中起始于偏移量 off 处取 len 个字节写入到输出流。
  • void close():关闭输出流。

FileInputStream 类和 FileOutputStream 类

  1. FileInputStream(String name)
  2. FileInputStream(File file)

第一个构造方法使用给定的文件名 name 创建一个 FileInputStream 对象。第二个构造方法使用 File 对象创建 FileInputStream 对象。参数 name 和 file 指定的文件称作输入流源,输入流通过调用 read 方法读出源中的数据。

try{    FilelnputStream istream=new FilelnputStream("myfile.dat");}catch(IOException e){    System.out.println("File read error:"+e);}

文件输入流构造方法的另一种格式是允许使用文件对象来指定要打开哪个文件,下面使用文件输入流构造方法建立一个文件输入流:

提示:

当使用文件输入流构造方法建立通往文件的输入流时,可能会出现异常。例如要打开的文件可能不存在。当出现 I/O 错误时,Java 会生成一个出错信号,它使用一个 IOException 对象来表示这个出错信号。
与 FilelnputStream 类相对应的类是 FileOutputStream 类。FileOutputStream 类提供基本的文件写入功能。除了从 OutputStream 类继承来的方法以外,FileOutputStream 类还有两个常用的构造方法,如下:

使用 FilelnputStream 的构造方法 FileInputStream(String name) 创建输入流时,以及使用 FileOutputStream 的构造方法 FileOutputStream(String name) 创建输出流时,如果参数仅仅是文件的名字(不带路径),就要保证参数表示的文件和当前应用程序在同一目录下,由于 引擎是在 bin 下启动执行的,所以文件必须在 bin 目录中。

BufferedInputStream 类和 BufferedOutputStream 类

FileInputStream 流经常和 BufferedInputStream 流配合使用,FileOutputStream 流经常和 BufferedOutputStream 流配合使用。BufferedInputStream 类的一个常用的构造方法是 BufferedInputStream(InputStream in),该构造方法创建缓存输入流。
当要读取一个文件,例如 A.txt 时,可以先建立一个指向该文件的文件输入流:

BufferedInputStream bufferRead=new BufferedInputStream(in);

这时,就可以让 bufferRead 调用 read 方法读取文件的内容。bufferRead 在读取文件过程中会进行缓存处理,提高读取的效率。

FileOutputStream out=new FileOutputStream("B.txt");

然后再创建一个指向输出流 out 的输出缓存流:

【例1】将若干内容写入一个文件,然后读取这个文件,并将文件的内容显示给用户:

6,什么是缓冲流?为什么要使用缓冲流?

解:流的分类

根据操作数据单位:字节流、字符流
根据数据流向:输入流、输出流
根据流的角色:节点流和处理流
2. 流的体系结构(只说重要的流)

抽象基类 节点流(文件流) 缓冲流(处理流的一种)
InputStream FileInputStream BufferedInputStream
OutputStream FileOutputStream BufferedoutputStream
Reader FileReader BufferedReader
Writer FileWriter BufferedWriter

引入缓冲的主要原因,可归结为以下几点:
1、改善CPU与I/O设备间速度不匹配的矛盾
2. 可以减少对 CPU的中断频率,放宽对中断响应时间的限制
3. 提高 CPU和 I/O设备之间的并行性
常见的缓冲技术有:单缓冲,双缓冲,循环缓冲,缓冲池。其中,广泛流行使用公用缓冲池。

7,字节流与字符流的区别及应用场合?

解:

stream结尾都是字节流,reader和writer结尾都是字符流
两者的区别就是读写的时候一个是按字节读写,一个是按字符。
实际使用通常差不多。
在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。
只是读写文件,和文件内容无关的,一般选择字节流

,如果参数仅仅是文件的名字(不带路径),就要保证参数表示的文件和当前应用程序在同一目录下,由于 引擎是在 bin 下启动执行的,所以文件必须在 bin 目录中。

BufferedInputStream 类和 BufferedOutputStream 类

FileInputStream 流经常和 BufferedInputStream 流配合使用,FileOutputStream 流经常和 BufferedOutputStream 流配合使用。BufferedInputStream 类的一个常用的构造方法是 BufferedInputStream(InputStream in),该构造方法创建缓存输入流。
当要读取一个文件,例如 A.txt 时,可以先建立一个指向该文件的文件输入流:

BufferedInputStream bufferRead=new BufferedInputStream(in);

这时,就可以让 bufferRead 调用 read 方法读取文件的内容。bufferRead 在读取文件过程中会进行缓存处理,提高读取的效率。

FileOutputStream out=new FileOutputStream("B.txt");

然后再创建一个指向输出流 out 的输出缓存流:

【例1】将若干内容写入一个文件,然后读取这个文件,并将文件的内容显示给用户:

6,什么是缓冲流?为什么要使用缓冲流?

解:流的分类

根据操作数据单位:字节流、字符流
根据数据流向:输入流、输出流
根据流的角色:节点流和处理流
2. 流的体系结构(只说重要的流)

抽象基类 节点流(文件流) 缓冲流(处理流的一种)
InputStream FileInputStream BufferedInputStream
OutputStream FileOutputStream BufferedoutputStream
Reader FileReader BufferedReader
Writer FileWriter BufferedWriter

引入缓冲的主要原因,可归结为以下几点:
1、改善CPU与I/O设备间速度不匹配的矛盾
2. 可以减少对 CPU的中断频率,放宽对中断响应时间的限制
3. 提高 CPU和 I/O设备之间的并行性
常见的缓冲技术有:单缓冲,双缓冲,循环缓冲,缓冲池。其中,广泛流行使用公用缓冲池。

7,字节流与字符流的区别及应用场合?

解:

stream结尾都是字节流,reader和writer结尾都是字符流
两者的区别就是读写的时候一个是按字节读写,一个是按字符。
实际使用通常差不多。
在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。
只是读写文件,和文件内容无关的,一般选择字节流

一些常见面试题

  • File类的常用方法?
  • 什么是方法迭代?
  • 什么是流?
  • 流的分类?
  • 使用字节流读取文件的步骤?
  • 什么是缓冲流?为什么要使用缓冲流?
  • 字节流与字符流的区别及应用场合?

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

终将站在顶峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值