精华内容
下载资源
问答
  • 万字长文深度剖析面向对象的javascript

    万次阅读 热门讨论 2020-12-02 09:46:56
    本将会深入讲解面向对象在javascript中的应用,并详细介绍三种对象的生成方式:构造函数,原型链,类。

    简介

    本将会深入讲解面向对象在javascript中的应用,并详细介绍三种对象的生成方式:构造函数,原型链,类。

    什么是对象

    虽然说程序员不缺对象,随时随地都可以new一个出来,但是在程序的世界中,对象到底是什么呢?

    对象是单个实物的抽象。

    对象是一个容器,封装了属性(property)和方法(method)。

    而面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。

    面向对象的好处就是可抽象,封装和可重用性,同时提供了继承和多态等非常有用的特性。

    而随着JS的发展,已经超越了最开始的脚本语言,尤其是nodejs的出现之后,更是极大的丰富了js的工作能力。

    所以JS也需要进行对象化。

    一般来说,在JS中构建对象有三种方式:

    • 构造函数(constructor)
    • 原型链(prototype)
    • 类(class) —ES6提供

    接下来,我们一一来讲解。

    构造函数

    构造函数是专门用来生成对象的函数。它提供模板,描述对象的基本结构。

    一个构造函数,可以生成多个对象,这些对象都有相同的结构。构造函数的写法就是一个普通的函数,但是有自己的特征和用法.

    var Book  = function () {
        this.name = 'www.flydean.com';
    }
    

    Book就是构造函数,它提供模板,用来生成实例对象。为了与普通函数区别,构造函数名字的第一个字母通常大写。

    构造函数的特点

    构造函数首先是一个函数,也就是说是function开头的函数。其次函数体内部使用了this关键字,代表了所要生成的对象实例。

    在使用构造函数的时候,必需用new命令,调用Book函数。

    new命令的作用,就是执行构造函数,返回一个实例对象。

    var Book  = function () {
        this.name = 'www.flydean.com';
    }
    
    var b1 = new Book();
    console.log(b1.name);
    

    上面的例子输出结果:

    www.flydean.com
    

    如果我们忘了使用new,会发生什么情况呢?

    var Book  = function () {
        this.name = 'www.flydean.com';
    }
    
    var b2 = Book();
    console.log(name);
    console.log(b2.name);
    

    第一个输出会输出www.flydean.com

    而第二个则会报一个错误:

    TypeError: Cannot read property 'name' of undefined
    

    因为这样调用的this指向的是global,所以this.name变成了全局变量。

    为了避免这种忘记写new的问题,可以在第一行加上use strict,在严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错。

    如果不想使用use strict,则可以在构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

    function Person(firstname,lastname){
    
        if(!(this instanceof Person)){
            return new Person(firstname,lastname);
        }
        this.firstname= firstname;
        this.firstname = lastname;
    }
    
    console.log(Person("jack","ma").firstname);
    console.log((new Person("jack","ma")).firstname);
    

    new命令的原理

    使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤:

    1. 创建一个空对象,作为将要返回的对象实例
    2. 将这个空对象的原型,指向构造函数的prototype属性
    3. 将这个空对象赋值给函数内部的this关键字
    4. 开始执行构造函数内部的代码

    如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。

    var Book  = function () {
        this.name = 'www.flydean.com';
        return {author:'flydean'};
    }
    
    console.log((new Book()).author);
    

    函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。

    通过new.target我们也可以用来判断对象是否通过new来创建:

    function f(){
        if(! new.target){
            throw new Error('请使用new命令!');
        }
    }
    f();
    

    构造函数作为模板,可以生成实例对象。但是,有时只能拿到实例对象,而该对象根本就不是由构造函数生成的,这时可以使用Object.create()方法,直接以某个实例对象作为模板,生成一个新的实例对象。

    var book2 = {
        name : '三毛流浪记',
        author : '三毛',
        getName : function () {
            console.log('book name is:' + this.name);
        }
    }
    var book3 = Object.create(book2);
    console.log(book3.name);
    book3.getName();
    

    prototype对象

    构造函数有什么缺点呢?构造函数的缺点就是会将构造函数内部的对象都复制一份:

    function Book(){
        this.name ='www.flydean.com';
        this.getName =function (){
            console.log('flydean');
        }
    }
    
    var book1 = new Book();
    var book2  = new Book();
    
    console.log(book1.getName  === book2.getName);
    

    输出结果是 false。说明每次new一个对象,对象中的方法也被拷贝了一份。而这并不是必须的。

    JavaScript 的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。

    原型对象上的所有属性和方法,都能被派生对象共享。这就是 JavaScript 继承机制的基本设计。

    通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。

    function Book(name){
        this.name = name;
    }
    
    Book.prototype.author ='flydean';
    var book1 = new Book();
    var book2 = new Book();
    console.log(book1.author);
    console.log(book2.author);
    

    上面例子中的author属性会被Book的所有实例所继承,Book的prototype对象,就是book1和book2的原型对象。

    原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。

    由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。

    如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性指向的那个对象。

    Object.prototype对象有没有它的原型呢?回答可以是有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。

    console.log(Object.getPrototypeOf(Object.prototype));
    //null
    

    prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数.

    function Book(name){
        this.name = name;
    }
    var book3 =new Book();
    console.log(book3.constructor);
    console.log(book3.constructor === Book.prototype.constructor);
    console.log(book3.hasOwnProperty(constructor));
    

    还是刚刚的book,book3.constructor就是function Book本身。它也等于Book.prototype.constructor。

    constructor属性的作用,是分辨原型对象到底属于哪个构造函数。

    因为prototype是一个对象,所以对象可以被赋值,也就是说prototype可以被改变:

    function A(){}
    var a = new A();
    console.log(a instanceof A);
    function B(){}
    A.prototype = B.prototype;
    console.log(a instanceof A);
    

    上面的例子中,我们修改了A.prototype,最后a instanceof A值是false。

    为了保证不会出现这样错误匹配的问题,我们再构建prototype的时候,一定不要直接重写整个的prototype,只需要修改其中的某个属性就好:

    //不要这样写
    A.prototype  ={
        method1:function (){}
    }
    
    //比较好的写法
    A.prototype  ={
        constructor:A,
        method1:function (){}
    }
    //更好的写法
    A.prototype.method1 = function (){}
    

    Object的prototype操作

    Object.getPrototypeOf

    Object.getPrototypeOf方法返回一个对象的原型。这是获取原型对象的标准方法.

    
    //空对象的prototype是Object.prototype
    console.log(Object.getPrototypeOf({}) === Object.prototype);
    
    //function的prototype是Function.prototype
    function f(){}
    console.log(Object.getPrototypeOf(f)  === Function.prototype);
    
    function F(){this.name ='flydean'}
    var f1 =new F();
    console.log(Object.getPrototypeOf(f1) === F.prototype);
    
    var f2 = new f();
    console.log(Object.getPrototypeOf(f2) === f.prototype);
    
    

    上面4个的输出结果都是true。

    Object.setPrototypeOf

    Object.setPrototypeOf方法可以为现有对象设置原型,返回一个新对象。

    Object.setPrototypeOf方法接受两个参数,第一个是现有对象,第二个是原型对象。

    var a = {name: 'flydean'};
    var b = Object.setPrototypeOf({},a);
    console.log(b.name);
    

    Object.prototype.isPrototypeOf()

    对象实例的isPrototypeOf方法,用来判断一个对象是否是另一个对象的原型.

    var a = {name: 'flydean'};
    var b = Object.setPrototypeOf({},a);
    console.log(a.isPrototypeOf(b));
    

    Object.prototype.proto

    __proto__属性(前后各两个下划线)可以改写某个对象的原型对象。

    还是刚才的例子,这次我们使用__proto__来改写对象的原型。

    var a = {name: 'flydean'};
    
    var c ={};
    c.__proto__ = a;
    console.log(Object.getPrototypeOf(c));
    

    __proto__属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。

    因此,应该尽量少用这个属性,而是用Object.getPrototypeof()(读取)和Object.setPrototypeOf()(设置),进行原型对象的读写操作。

    三种获取原型对象的方法

    综上,我们有三种获取原型对象的方法:

    • obj.proto
    • obj.constructor.prototype
    • Object.getPrototypeOf(obj)

    this对象

    this总是返回一个对象,简单说,就是返回属性或方法“当前”所在的对象。

    var book = {
        name :'flydean',
        getName : function (){
            return '书名:'+ this.name;
        }
    }
    
    console.log(book.getName());
    //书名:flydean
    

    这里this的指向是可变的,我们看一个例子 :

    var book = {
        name :'flydean',
        getName : function (){
            return '书名:'+ this.name;
        }
    }
    
    var car ={
        name :'car'
    }
    
    car.getName = book.getName;
    console.log(car.getName());
    //书名:car
    

    当 A 对象的方法被赋予 B 对象,该方法中的this就从指向 A 对象变成了指向 B 对象

    上面的例子中,我们把book中的getName方法赋值给了car对象,this对象现在就指向了car。

    如果某个方法位于多层对象的内部,这时this只是指向当前一层的对象,而不会继承更上面的层。

    var book1 = {
        name :'flydean',
        book2: {
            getName : function (){
                return '书名:'+ this.name;
            }
        }
    }
    console.log(book1.book2.getName());
    //书名:undefined
    

    上面的例子中,this是定义在对象中的函数中,如果是在函数中的函数中定义的this,代表什么呢?

    var book3 = {
        name :'flydean',
        book4: function(){
            console.log('book4');
            var getName = function (){
                console.log(this); //Window
            }();
        }
    }
    book3.book4();
    

    如果在函数中的函数中使用了this,那么内层的this指向的是全局的window对象。

    所以我们在使用的过程中要避免多层 this。由于this的指向是不确定的,所以切勿在函数中包含多层的this。

    如果在全局环境使用this,它指的就是顶层对象window。

    数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。

    var book5 ={
        name : 'flydean',
        author : ['max','jacken'],
        f: function (){
            this.author.forEach(function (item) {
                console.log(this.name+' '+item);
            })
        }
    }
    book5.f();
    //undefined max
    //undefined jacken
    

    foreach方法的回调函数中的this,其实是指向window对象,因此取不到o.v的值。原因跟上一段的多层this是一样的,就是内层的this不指向外部,而指向顶层对象。

    怎么解决呢?我们使用一个中间变量:

    var book6 ={
        name : 'flydean',
        author : ['max','jacken'],
        f: function (){
            var that = this;
            this.author.forEach(function (item) {
                console.log(that.name+' '+item);
            })
        }
    }
    book6.f();
    //flydean max
    //flydean jacken
    

    或者将this当作foreach方法的第二个参数,固定它的运行环境:

    var book7 ={
        name : 'flydean',
        author : ['max','jacken'],
        f: function (){
            this.author.forEach(function (item) {
                console.log(this.name+' '+item);
            },this)
        }
    }
    book7.f();
    //flydean max
    //flydean jacken
    

    绑定this的方法

    JavaScript提供了call、apply、bind这三个方法,来切换/固定this的指向.

    call

    函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数.

    var book = {};
    
    var f = function () {
        return this;
    }
    f()  === this ; //true
    f.call(book) === book; //true
    

    上面例子中,如果直接调用f(),那么返回的就是全局的window对象。如果传入book对象,那么返回的就是book对象。

    call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。

    如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。

    var f = function () {
        return this;
    }
    
    console.log(f.call(100));
    //[Number: 100]
    

    call方法还可以接受多个参数.

    func.call(thisValue,arg1,arg2, ...);
    

    call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

    call一般用在调用对象的原始方法:

    var person =  {};
    
    person.hasOwnProperty('getName');//false
    
    //覆盖person的getName方法
    person.getName  = function(){
        return true;
    }
    
    person.hasOwnProperty('getName');//true
    Object.prototype.hasOwnProperty.call(person,'getName');//false
    

    apply

    apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数.

    func.apply(thisValue,[arg1,arg2,...])
    

    bind

    call和apply是改变this的指向,然后调用该函数,而bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数.

    var d = new Date();
    
    console.log(d.getTime()); //1600755862787
    
    var getTime= d.getTime;
    console.log(getTime());//TypeError: this is not a Date object.
    

    上面的例子中,getTime方法里面调用了this,如果直接把d.getTime赋值给getTime变量,那么this将会指向全局的window对象,导致运行错误。

    我们可以这样修改:

    var d = new Date();
    
    console.log(d.getTime()); //1600755862787
    
    var getTime2= d.getTime.bind(d);
    console.log(getTime2());
    

    bind比call方法和apply方法更进一步的是,除了绑定this以外,还可以绑定原函数的参数。

    var add = function(x,y){
        return x +this.m +  y + this.n;
    }
    var addObj ={
        m: 10,
        n: 10
    }
    
    var newAdd = add.bind(addObj,2);
    console.log(newAdd(3));//25
    

    上面的例子中,bind将两个参数的add方法,替换成了1个参数的add方法。

    注意,bind每次调用都会返回一个新的函数,从而导致无法取消之前的绑定。

    继承

    构造函数的继承

    构造函数的继承第一步是在子类的构造函数中,调用父类的构造函数,让子类实例具有父类实例的属性。

    然后让子类的原型指向父类的原型,这样子类就可以继承父类原型。

    function Person (){
        this.name = 'person';
    }
    
    function Boy(){
        Person.call(this);
        this.title = 'boy';
    }
    
    Boy.prototype= Object.create(Person.prototype);
    Boy.prototype.constructor=Boy;
    Boy.prototype.getTitle=function (){console.log(this.title)};
    
    var b =new Boy();
    b.getTitle();
    console.log(b);
    ~~
    
    调用父类的构造函数是初始化实例对象的属性。子类的原型指向父类的原型是为了基础父类的原型对象的属性。
    
    另外一种写法是Boy.prototype等于一个父类实例:
    
    ~~~js
    Boy.prototype = new Person();
    

    上面这种写法也有继承的效果,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法.

    JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能:

    function Person1 (){
        this.name = 'person';
    }
    function Person2 (){
        this.sex = '男';
    }
    
    function Boy(){
        Person1.call(this);
        Person2.call(this);
        this.title = 'boy';
    }
    
    //继承Person1
    Boy.prototype= Object.create(Person1.prototype);
    //继承链加上Person2
    Object.assign(Boy.prototype,Person2.prototype);
    
    Boy.prototype.constructor=Boy;
    Boy.prototype.getTitle=function (){console.log(this.title)};
    
    var b =new Boy();
    b.getTitle();
    console.log(b);
    //Boy { name: 'person', sex: '男', title: 'boy' }
    

    class

    ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已.

    class Person {
        constructor(name,sex) {
            this.name=name;
            this.sex =sex;
        }
    
        toString(){
            return this.name + ' '+ this.sex;
        }
    }
    

    构造函数的prototype属性,在ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

    上面的类等同于:

    Person.prototype = {
           constructor(name,sex) {
            this.name=name;
            this.sex =sex;
        }
    
        toString(){
            return this.name + ' '+ this.sex;
        } 
    }
    

    表达式属性名

    class还支持动态的表达式属性名:

    let methodName = 'getName';
    
    class Person {
        constructor(name,sex) {
            this.name=name;
            this.sex =sex;
        }
    
        toString(){
            return this.name + ' '+ this.sex;
        }
    
        [methodName](){
            return this.name;
        }
    }
    

    静态方法

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    class Person {
        constructor(name,sex) {
            this.name=name;
            this.sex =sex;
        }
    
        static getSex(){
            return '男';
        }
    }
    
    console.log(Person.getSex()); //男
    
    let  p  = new Person();
    console.log(p.getSex());//TypeError: p.getSex is not a function
    

    静态属性

    静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性.

    
    class Person {
        constructor(name,sex) {
            this.name=name;
            this.sex =sex;
        }
    }
    Person.address ='address';
    console.log(Person.address);
    

    目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性.

    class的继承

    class的继承一般使用extends关键字:

    class Boy extends Person{
        constructor(name,sex,address) {
            super(name,sex); //调用父类的构造函数
            this.address =address;
        }
    
        toString() {
            return super.toString();//调用父类的方法
        }
    }
    

    在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

    super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

    super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

    上面的例子,我们在子类Boy中的toString普通方法中,调用了super.toString(),之前我们也讲了,类的所有方法都定义在类的prototype属性上面。所以super.toString就是Person中定义的toString方法。

    由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

    定义在父类实例上的方法或属性就是指在constructor中定义的方法或者属性。

    Person类,在constructor中定义了name属性。我们看一下在Boy中的普通方法中访问会有什么问题:

    class Boy extends Person{
        constructor(name,sex,address) {
            super(name,sex); //调用父类的构造函数
            console.log(super.name);  //undefined
            console.log(this.name);  //hanmeimei
            this.address =address;
        }
    
        toString() {
            return super.toString();//调用父类的方法
        }
    
        getName(){
            console.log(super.name);  //undefined
            console.log(this.name);    //hanmeimei
        }
    }
    
    var b =new Boy('hanmeimei','女','北京');
    b.getName();
    

    总结

    JS中的面向对象主要有构造函数,原型链,类三种方式,希望大家能够喜欢。

    本文作者:flydean程序那些事

    本文链接:http://www.flydean.com/object-oriented-js/

    本文来源:flydean的博客

    欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    展开全文
  • R语言面向对象指南

    千次阅读 2015-09-21 21:53:00
    面向对象指南:这一章主要介绍怎样识别和使用 R 语言的面向对象系统(以下简称 OO)。R 语言主要有三种 OO 系统(加上基本类型)。本指南的目的不是让你精通 R 语言的 OO,而是让你熟悉各种系统,并且能够准确地区分...

    原文链接:OO field guide


    面向对象指南:

    这一章主要介绍怎样识别和使用 R 语言的面向对象系统(以下简称 OO)。R 语言主要有三种 OO 系统(加上基本类型)。本指南的目的不是让你精通 R 语言的 OO,而是让你熟悉各种系统,并且能够准确地区分和使用它们。
    OO 最核心的就是类和方法的思想,类在定义对象的行为时主要是通过对象的属性以及它和其它类之间的关系。根据类的输入不同,类对方法、函数的选择也会不同。类的建造是有层次结构的:如果一个方法在子类中不存在,则使用父类中的方法;如果存在则继承父类中方法。

    三种 OO 系统在定义类和方法的时候有以下不同:

    • S3 实现的是泛型函数式 OO ,这与大部分的编程语言不同,像 Java、C++ 和 C# 它们实现的是消息传递式的 OO 。如果是消息传递,消息(方法)是传给一个对象,再由对象去决定调用哪个方法的。通常调用方法的形式是“对象名.方法名”,例如:canvas.drawRect(“blue”) 。而 S3 不同,S3 调用哪个方法是由泛型函数决定的,例如:drawRect(canvas, “blue”)。S3 是一种非正式的 OO 模式,它甚至都没有正式定义类这个概念。
    • S4 与 S3 很相似,但是比 S3 正规。S4 与 S3 的不同主要有两点:S4 对类有更加正式的定义(描述了每个类的表现形式和继承情况,并且对泛型和方法的定义添加了特殊的辅助函数);S4 支持多调度(这意味着泛型函数在调用方法的时候可以选择多个参数)。
    • Reference classes (引用类),简称 RC ,和 S3、S4有很大区别。RC 实现的是消息传递式 OO ,所以方法是属于类的,而不是函数。对象和方法之间用”$”隔开,所以调用方法的形式如:canvas$drawRect(“blue”) 。RC 对象也总是可变的,它用的不是 R 平常的 copy-on-modify 语义,而是做了部分修改。从而可以解决 S3、S4 难以解决的问题。

    还有另外一种系统,虽然不是完全的面向对象,但还是有必要提一下:

    • base types(基本类型),主要使用C语言代码来操作。它之所以重要是因为它能为其它 OO 系统提供构建块。

    以下内容从基本类型开始,逐个介绍每种 OO 系统。你将学习到怎样识别一个对象是属于哪种 OO 系统、方法的调用和使用,以及在该 OO 系统下如何创建新的对象、类、泛型和方法。本章节的结尾也有讲述哪种情况应该使用哪种系统。

    前提:

    你首先需要安装 pryr 包来获取某些函数:install.packages(“pryr”) 。

    问题:

    你是否已经了解本文要讲述的内容?如果你能准确地回答出以下问题,则可以跳过本章节了。答案请见本文末尾的问题答案

    1. 你怎样区分一个对象属于哪种 OO 系统?
    2. 如何确定基本类型(如整型或者列表)的对象?
    3. 什么是类的函数?
    4. S3 和 S4 之间的主要差异是什么? S4 和 RC 之间最主要的差异又是什么?

    文章梗概:


    基本类型:

    基本上每个 R 对象都类似于描述内存存储的 C 语言结构体,这个结构体包含了对象的所有内容(包括内存管理需要的信息,还有对象的基本类型)。基本类型并不是真的对象系统,因为只有 R 语言的核心团队才能创建新的类型。但结果新的基本类型竟然也很少见地被添加了:最近是在2011年,添加了两个你从来没在 R 里面见过的奇异类型(NEWSXP 和 FREESXP),它们能够有效地诊断出内存上的问题。在此之前,2005年为 S4 对象添加了一个特殊的基本类型(S4SXP)。

    Data structures 章节讲述了大部分普通的基本类型(原子向量和列表),但基本类型还包括 functions、environments,以及其它更加奇异的对象,如 names、calls、promises,之后你将会在本书中学到。你可以使用 typeof() 来了解对象的基本类型。但基本类型的名字在 R 中并不总是有效的,并且类型和 “is” 函数可能会使用不同的名字:

    # The type of a function is "closure"
    f <- function() {}
    typeof(f)
    #> [1] "closure"
    is.function(f)
    #> [1] TRUE
    
    # The type of a primitive function is "builtin"
    typeof(sum)
    #> [1] "builtin"
    is.primitive(sum)
    #> [1] TRUE

    你可能听过 mode() 和 storage.mode(),我建议不要使用这两个函数,因为它们只是 typeof() 返回值的别名,而且只使用与 S 语言。如果你想了解它们具体如何实现,可以去看一下它们的源代码。

    不同基本类型的函数一般都是用 C 语言编写的,在调度时使用switch语句(例如:switch(TYPEOF(x)))。尽管你可能没有写过 C 语言,但理解基本类型仍然很有必要,因为其他系统都是在此基础上的:S3 对象可以建立在所有基本类型上,S4 使用一个特殊的基本类型,而 RC 对象是 S4 和 environments(一个特殊的基本类型)的结合体。查看对象是否是一个单纯基本类型(即它不同时含 S3、S4、RC 的行为),使用 is.object(x) ,返回TRUE/FALSSE。


    S3:

    S3 是 R 语言的第一种也是最简单的一种 OO 系统。它还是唯一一种在基础包和统计包使用的 OO 系统,CRAN包中最平常使用的 OO 系统。

    识别对象、泛型函数、方法:

    你遇到的大部分对象都是 S3 对象。但不幸的是在 R 中并没有可以简单检测一个方法是否是 S3 的方法。最接近的方法就是 is.object(x) & !isS4(x),即它是一个对象,但不是 S4 对象。一个更简单的方法就是使用 pryr::otype() :

    library(pryr)
    
    df <- data.frame(x = 1:10, y = letters[1:10])
    otype(df)    # A data frame is an S3 class
    #> [1] "S3"
    otype(df$x)  # A numeric vector isn't
    #> [1] "base"
    otype(df$y)  # A factor is
    #> [1] "S3"

    在 S3,方法是属于函数的,这些函数叫做泛型函数,或简称泛型。S3 的方法不属于对象或者类。这和大部分的编程语言都不同,但它确实是一种合法的 OO 方式。

    你可以调用 UseMethod() 方法来查看某个函数的源代码,从而确定它是否是 S3 泛型。和 otype() 类似,prpy 也提供了 ftype() 来联系着一个函数(如果有的话)描述对象系统。

    mean
    #> function (x, ...) 
    #> UseMethod("mean")
    #> <bytecode: 0x24bfa50>
    #> <environment: namespace:base>
    ftype(mean)
    #> [1] "s3"      "generic"

    有些 S3 泛型,例如 [ 、sum()、cbind(),不能调用 UseMethod(),因为它们是用 C 语言来执行的。不过它们可以调用 C 语言的函数 DispatchGroup() 和 DispatchOrEval()。利用 C 代码进行方法调用的函数叫作内部泛型。可以使用 ?”internal generic” 查看。

    给定一个类,S3 泛型的工作是调用正确的 S3 方法。你可以通过 S3 方法的名字来识别(形如 generic.class())。例如,泛型 mean() 的 Date 方法为 mean.Date(),泛型print() 的向量方法为 print.factor() 。
    这也就是为什么现代风格不鼓励在函数名字里使用 “.” 的原因了。类的名字也不使用 “.” 。pryr::ftype() 可以发现这些异常,所以你可以用它来识别一个函数是 S3 方法还是泛型:

    ftype(t.data.frame) # data frame method for t()
    #> [1] "s3"     "method"
    ftype(t.test)       # generic function for t tests
    #> [1] "s3"      "generic"

    你可以调用 methods() 来查看属于某个泛型的所有方法:

    methods("mean")
    #> [1] mean.Date     mean.default  mean.difftime mean.POSIXct  mean.POSIXlt 
    #> see '?methods' for accessing help and source code
    methods("t.test")
    #> [1] t.test.default* t.test.formula*
    #> see '?methods' for accessing help and source code

    (除了在基础包里面定义的一些方法,大多数 S3 的方法都是不可见的使用 getS3method() 来阅读它们的源码。)

    你也可以列出一个给出类中包含某个方法的所有泛型:

    methods(class = "ts")
    #>  [1] aggregate     as.data.frame cbind         coerce        cycle        
    #>  [6] diffinv       diff          initialize    kernapply     lines        
    #> [11] Math2         Math          monthplot     na.omit       Ops          
    #> [16] plot          print         show          slotsFromS3   time         
    #> [21] [<-           [             t             window<-      window       
    #> see '?methods' for accessing help and source code

    你也可以从接下来的部分知道,要列出所有的 S3 类是不可能的。

    定义类和创建对象:

    S3 是一个简单而特殊的系统,它对类没有正式的定义。要实例化一个类,你只能拿一个已有的基础对象,再设置类的属性。你可以在创建类的时候使用 structure(),或者事后用 class<-():

    # Create and assign class in one step
    foo <- structure(list(), class = "foo")
    
    # Create, then set class
    foo <- list()
    class(foo) <- "foo"

    S3 对象的属性通常建立在列表或者原子向量之上(你可以用这个属性去刷新你的内存属性),你也能把函数转成 S3 对象,其他基本类型要么在 R 中很少见,要么就是该语义不能很好地在属性下运行。
    你可以通过 class() 把类看作任意的对象,也可以通过 inherits(x, “classname”) 来查看某个对象是否继承自某个具体的类。

    class(foo)
    #> [1] "foo"
    inherits(foo, "foo")
    #> [1] TRUE

    S3 对象所属于的类可以被看成是一个向量,一个通过最重要的特性来描述对象行为的向量。例如对象 glm() 的类是 c(“glm”, “lm”),它表明着广义线性模型的行为继承自线性模型。类名通常是小写的,并且应该避免使用 “.” 。否则该类名将会混淆为下划线形式的 my_class,或者 CamelCase 写法的 MyClass。

    大多数的 S3 类都提供了构造函数:

    foo <- function(x) {
      if (!is.numeric(x)) stop("X must be numeric")
      structure(list(x), class = "foo")
    }

    如果它是可用的,则你应该使用它(例如 factor() 和 data.frame())。这能确保你在创造类的时候使用正确的组件。构造函数的名字一般是和类名是相同的。

    开发者提供了构造函数之后,S3 并没有对它的正确性做检查。这意味着你可以改变现有对象所属于的类:

    # Create a linear model
    mod <- lm(log(mpg) ~ log(disp), data = mtcars)
    class(mod)
    #> [1] "lm"
    print(mod)
    #> 
    #> Call:
    #> lm(formula = log(mpg) ~ log(disp), data = mtcars)
    #> 
    #> Coefficients:
    #> (Intercept)    log(disp)  
    #>      5.3810      -0.4586
    
    # Turn it into a data frame (?!)
    class(mod) <- "data.frame"
    # But unsurprisingly this doesn't work very well
    print(mod)
    #>  [1] coefficients  residuals     effects       rank          fitted.values
    #>  [6] assign        qr            df.residual   xlevels       call         
    #> [11] terms         model        
    #> <0 rows> (or 0-length row.names)
    # However, the data is still there
    mod$coefficients
    #> (Intercept)   log(disp) 
    #>   5.3809725  -0.4585683

    如果你在之前使用过其他的 OO 语言,S3 可能会让你觉得很恶心。但令人惊讶的是,这种灵活性带来的问题很少:虽然你能改变对象的类型,但你并不会这么做。R 并不用提防自己:你可以很容易射自己的脚,只要你不把抢瞄在你的脚上并扣动扳机,你就不会有问题。

    创建新的方法和泛型:

    如果要添加一个新的泛型,你只要创建一个叫做 UseMethod() 的函数。UseMethod() 有两个参数:泛型函数的名字和用来调度方法的参数。如果第二个参数省略了,则根据第一个参数来调度方法。但是没有必要去省略 UseMethod() 的参数,你也不应该这么做。

    f <- function(x) UseMethod("f")

    没有方法的泛型是没有用的。如果要添加方法,你只需要用 generic.class 创建一个合法的函数:

    f.a <- function(x) "Class a"
    
    a <- structure(list(), class = "a")
    class(a)
    #> [1] "a"
    f(a)
    #> [1] "Class a"

    用同样的方法可以对已有的泛型添加方法:

    mean.a <- function(x) "a"
    mean(a)
    #> [1] "a"

    如你所看到的,它并没有确保类和泛型兼容的检查机制,它主要是靠编程者自己来确定自己的方法不会违反现有代码的期望。

    方法调度:

    S3 的方法调度比较简单。UseMethod() 创建一个向量或者一个函数名字(例如:paste0(“generic”, “.”, c(class(x), “default”))),并逐个查找。default 类作为回落的方法,以防其他未知类的情况。

    f <- function(x) UseMethod("f")
    f.a <- function(x) "Class a"
    f.default <- function(x) "Unknown class"
    
    f(structure(list(), class = "a"))
    #> [1] "Class a"
    # No method for b class, so uses method for a class
    f(structure(list(), class = c("b", "a")))
    #> [1] "Class a"
    # No method for c class, so falls back to default
    f(structure(list(), class = "c"))
    #> [1] "Unknown class"

    组泛型方法增加了一些复杂性,组泛型为一个函数实现复合泛型的多个方法提供了可能性。它们包含的四组泛型和函数如下:

    • Math: abs, sign, sqrt, floor, cos, sin, log, exp, …
    • Ops: +, -, *, /, ^, %%, %/%, &, |, !, ==, !=, <, <=, >=, >
    • Summary: all, any, sum, prod, min, max, range
    • Complex: Arg, Conj, Im, Mod, Re

    组泛型是相对比较先进的技术,超出了本章的范围。但是你可以通过 ?groupGeneric 查看更多相关信息。区分组泛型最关键的是要意识到 Math、Ops、Summary 和 Complex 并不是真正的函数,而是代表着函数。注意在组泛型中有特殊的变量 .Generic 提供实际的泛型函数调用。

    如果你有复数类模板的层次结构,那么调用“父”方法是有用的。要准确定义它的意义的话有点难度,但如果当前方法不存在的话它基本上都会被调用。同样的,你可以使用 ?NextMethod 查看相关信息。

    因为方法是正规的 R 函数,所以你可以直接调用它:

    c <- structure(list(), class = "c")
    # Call the correct method:
    f.default(c)
    #> [1] "Unknown class"
    # Force R to call the wrong method:
    f.a(c)
    #> [1] "Class a"

    不过这种调用的方法和改变对象的类属性一样危险,所以一般都不这样做。不要把上膛了的枪瞄在自己的脚上。使用上述方法的唯一原因是它可以通过跳过方法调用达到很大的性能改进,你可以查看性能章节查看详情。

    非 S3 对象也可以调用 S3 泛型,非内部的泛型会调用基本类型的隐式类。(因为性能上的原因,内部的泛型并不会这样做。)确定基本类型的隐式类有点难,如下面的函数所示:

    iclass <- function(x) {
      if (is.object(x)) {
        stop("x is not a primitive type", call. = FALSE)
      }
    
      c(
        if (is.matrix(x)) "matrix",
        if (is.array(x) && !is.matrix(x)) "array",
        if (is.double(x)) "double",
        if (is.integer(x)) "integer",
        mode(x)
      )
    }
    iclass(matrix(1:5))
    #> [1] "matrix"  "integer" "numeric"
    iclass(array(1.5))
    #> [1] "array"   "double"  "numeric"

    练习:

    1. 查阅 t() 和 t.test() 的源代码,并证明 t.test() 是一个 S3 泛型而不是 S3 方法。如果你用 test 类创建一个对象并用它调用 t() 会发生什么?
    2. 在 R 语言的基本类型中什么类有 Math 组泛型?查阅源代码,该方法是如何工作的?
    3. R 语言在日期时间上有两种类,POSIXct 和 POSIXlt(两者都继承自 POSIXt)。哪些泛型对于这两个类是有不同行为的?哪个泛型共享相同的行为?
    4. 哪个基本泛型定义的方法最多?
    5. UseMethod() 通过特殊的方式调用方法。请预测下列代码将会返回什么,然后运行一下,并且查看 UseMethod() 的帮助文档,推测一下发生了什么。用最简单的方式记下这些规则。
    y <-1
    g <-function(x) {
      y <-2UseMethod("g")
    }
    g.numeric <-function(x) y
    g(10)
    
    h <-function(x) {
      x <-10UseMethod("h")
    }
    h.character <-function(x) paste("char", x)
    h.numeric <-function(x) paste("num", x)
    
    h("a")
    1. 内部泛型不分配在基类类型的隐式类。仔细查阅 ?”internal generic”,为什么下面例子中的 f 和 g 的长度不一样?哪个函数可以区分 f 和 g 的行为?
    f <- function() 1
    g <- function() 2
    class(g) <- "function"
    
    class(f)
    class(g)
    
    length.function <- function(x) "function"
    length(f)
    length(g)


    S4:

    S4 工作的方式和 S3 比较相似,但它更加正式和严谨。方法还是属于函数,而不是类。但是:

    • 类在描述字段和继承结构(父类)上有更加正式的定义。
    • 方法调用可以传递多个参数,而不仅仅是一个。
    • 出现了一个特殊的运算符——@,从 S4 对象中提取 slots(又名字段)。

    所以 S4 的相关代码都存储在 methods 包里面。当你交互运行 R 程序的时候这个包都是可用的,但在批处理的模式下则可能不可用。所以,我们在使用 S4 的时候一般直接使用 library(methods) 。
    S4 是一种丰富、复杂的系统,并不是一两页纸能解释完的。所以在此我把重点放在 S4 背后的面向对象思想,这样大家就可以比较好地使用 S4 对象了。如果想要了解更多,可以参考以下文献:

    • S4 系统在 Bioconductor 中的发展历程
    • John Chambers 写的《Software for Data Analysis》
    • Martin Morgan 在 stackoverflow 上关于 S4 问题的回答

    识别对象、泛型函数和方法:

    要识别 S4 对象 、泛型、方法还是很简单的。对于 S4 对象:str() 将它描述成一个正式的类,isS4() 会返回 TRUE,prpy::otype() 会返回 “S4” 。对于 S4 泛型函数:它们是带有很好类定义的 S4 对象。
    常用的基础包里面是没有 S4 对象的(stats, graphics, utils, datasets, 和 base),所以我们要从内建的 stats4 包新建一个 S4 对象开始,它提供了一些 S4 类和方法与最大似然估计:

    library(stats4)
    
    # From example(mle)
    y <- c(26, 17, 13, 12, 20, 5, 9, 8, 5, 4, 8)
    nLL <- function(lambda) - sum(dpois(y, lambda, log = TRUE))
    fit <- mle(nLL, start = list(lambda = 5), nobs = length(y))
    
    # An S4 object
    isS4(fit)
    #> [1] TRUE
    otype(fit)
    #> [1] "S4"
    
    # An S4 generic
    isS4(nobs)
    #> [1] TRUE
    ftype(nobs)
    #> [1] "s4"      "generic"
    
    # Retrieve an S4 method, described later
    mle_nobs <- method_from_call(nobs(fit))
    isS4(mle_nobs)
    #> [1] TRUE
    ftype(mle_nobs)
    #> [1] "s4"     "method"

    用带有一个参数的 is() 来列出对象继承的所有父类。用带有两个参数的 is() 来验证一个对象是否继承自该类:

    is(fit)
    #> [1] "mle"
    is(fit, "mle")
    #> [1] TRUE

    你可以使用 getGenerics() 来获取 S4 的所有泛型函数,或者使用 getClasses() 来获取 S4 的所有类。这些类包括 S3 对 shim classes 和基本类型。另外你可以使用 showMethods() 来获取 S4 的所有方法。

    定义类和新建对象

    在 S3,你可以通过更改类的属性就可以改变任意一个对象,但是在 S4 要求比较严格:你必须使用 setClass() 定义类的声明,并且用 new() 新建一个对象。你可以用特殊的语法 class?className(例如:class?mle)找到该类的相关文档。
    S4 类有三个主要的特性:

    • 名字:一个字母-数字的类标识符。按照惯例,S4 类名称使用 UpperCamelCase 。
    • 已命名的 slots(字段),它用来定义字段名称和允许类。例如,一个 person 类可能由字符型的名称和数字型的年龄所表征:list(name = "character", age = "numeric")
    • 父类。你可以给出多重继承的多个类,但这项先进的技能增加了它的复杂性。

    slotscontains,你可以使用setOldClass()来注册新的 S3 或 S4 类,或者基本类型的隐式类。在slots,你可以使用特殊的ANY类,它不限制输入。
    S4 类有像 validity 方法的可选属性,validity 方法可以检验一个对象是否是有效的,是否是定义了默认字段值的 prototype 对象。使用?setClass查看更多细节。
    下面的例子新建了一个具有 name 字段和 age 字段的 Person 类,还有继承自 Person 类的 Employee 类。Employee 类从 Person 类继承字段和方法,并且增加了字段 boss 。我们调用 new() 方法和类的名字,还有name-values这样成对的参数值来新建一个对象。

    setClass("Person",
      slots = list(name = "character", age = "numeric"))
    setClass("Employee",
      slots = list(boss = "Person"),
      contains = "Person")
    
    alice <- new("Person", name = "Alice", age = 40)
    john <- new("Employee", name = "John", age = 20, boss = alice)

    大部分 S4 类都有一个和类名相同名字的构造函数:如果有,可以直接用它来取代 new()
    要访问 S4 对象的字段,可以用 @ 或者 slot()

    alice@age
    #> [1] 40
    slot(john, "boss")
    #> An object of class "Person"
    #> Slot "name":
    #> [1] "Alice"
    #> 
    #> Slot "age":
    #> [1] 40

    @$ 等价,slot()[] 等价)
    如果一个 S4 对象继承自 S3 类或者基本类型,它会有特殊的属性 .Data

    setClass("RangedNumeric",
      contains = "numeric",
      slots = list(min = "numeric", max = "numeric"))
    rn <- new("RangedNumeric", 1:10, min = 1, max = 10)
    rn@min
    #> [1] 1
    rn@.Data
    #>  [1]  1  2  3  4  5  6  7  8  9 10

    因为 R 是响应式编程的语言,所以它可以随时创建新的类或者重新定义现有类。这将会造成一个问题:当你在响应式地调试 S4 的时候,如果你更改了一个类,你要知道你已经把该类的所有对象都更改了。

    新建方法和泛型函数

    S4 提供了特殊的函数来新建方法和泛型。setGeneric() 将产生一个新的泛型,或者把已有函数转成泛型。

    setGeneric("union")
    #> [1] "union"
    setMethod("union",
      c(x = "data.frame", y = "data.frame"),
      function(x, y) {
        unique(rbind(x, y))
      }
    )
    #> [1] "union"

    如果你要重新创建了一个泛型,你需要调用 standardGeneric() :

    setGeneric("myGeneric", function(x) {
      standardGeneric("myGeneric")
    })
    #> [1] "myGeneric"

    S4 中的 standardGeneric() 相当于 UseMethod()


    测试的答案

    1. 要确定一个对象属于哪种面向对象系统,你可以用排除法,如果 !is.object(x) 返回 TRUE,那么它是一个基本对象。如果 !isS4(x) 返回 TRUE,那么它是一个 S3 。如果 !is(x, "refClass") 返回 TRUE, 那么它是一个 S4 ,否则它是 RC 。
    2. typeof() 来确定基本类型的对象。
    3. 泛型函数调用特殊方法的时候主要是通过它的参数输入来确定的,在 S3 和 S4 系统,方法属于泛型函数,不像其他编程语言那样属于类。
    4. S4 比 S3 更加正式,并且支持多重继承和多重调度,RC 对象的语义和方法是属于类的,而不属于函数。
    展开全文
  • Java面向对象基础

    万次阅读 多人点赞 2015-11-05 16:33:28
    计算机语言的发展由面向机器向面向对象发展的,是越来越符合人的思维习惯的。   符合人类思维习惯程度 低 → 高  面向机器 汇编语言 面向过程 C语言 面向对象 Java语言     类和

    面向对象编程基础

    1、类与对象

    java语言是面向对象的

    计算机语言的发展由面向机器向面向对象发展的,是越来越符合人的思维习惯的。

     

    符合人类思维习惯程度

    低       →       高 

    面向机器

    汇编语言

    面向过程

    C语言

    面向对象

    Java语言

     

     

    类和对象的关系

    类是对象抽象出来的概念性的通用性的东西。

     

    实例和对象的关系

    (对象就是实例,实例就是对象)java最大的特点就是面向对象。


    //定义类
    public class Test{
    	public static void main(String []args){
    		//创建一个猫对象
    		Cat cat1=new Cat();//Cat是定义的一个数据类型
    		//Cat cat1;
    		//cat1=new Cat();// 等同于 Cat cat1=new Cat();
    		//访问属性的 对象名.属性名字
    		cat1.age=3;
    		cat1.name="小白";
    		cat1.color="白色";
    
    		//创建第二只猫
    		Cat cat2=new Cat();
    		cat2.age=100;
    		cat2.name="小花";
    		cat2.color="花色";  
    	}
    }
    class Cat{
    	//下面的就是类的成员变量/属性
    	int agr;
    	String name;
    	String color;
    }


    类和对象的区别和联系

    1、类是抽象的,概念的,代表一类事物,比如人类,动物类..

    2、对象是具体的,实际的,代表一个具体事物比如人类的对象“张三”..

    3、类对象的模板,对象是类的一个具体实例

     

    类--如何定义类

    一个全面的类定义比较复杂,如下:

    package 包名;
    class 类名 extends 父类 implements 接口名{
     	成员变量;
     	构造方法;
    成员方法;
    }

    类--类的成员变量

    成员变量是类的一个组成部分,一般是基本数据类型,也可是引用类型。

     

    对象--如何创建对象

    创建一个对象有两种方法

    1、先声明再创建

      1、对象声明:类名 对象名

      2、对象创建:对象名=new 类名()

    如下:

    Cat cat;
    cat = new Cat();

    2、声明创建一起

       类名 对象名=new 类名()

    如下:

    Cat cat = new Cat();

    对象--如何访问(使用)对象的成员变量

       对象名.变量名; //先这样理解,以后加下访问控制符此表达就不准确了。

    如下:

    cat.name = “小花”;

    ***对象总是存在内存中的(具体应该在系统分配给JVM的堆区)


    类--类的成员方法定义

    成员方法也叫成员函数。

    public 返回数据类型 方法名(参数列表)
    	{
    		语句;//方法(函数)主体
    	}

    1、参数列表:表示成员方法的输入参数

    2、返回数据类型:表示成员方法的输出

    3、方法主体:表示实现某一功能代码块

     

    定义名字的规范写法:(PS:很多类型在之后的内容中会展示,这里做个总结)

    类型

    命名规范

    关键字

    举例

    类名

    首字母大写

    class

    MyClass

    变量和方法

    (除第一个单词外)首字母大写

    int,byte,String等等

    cat,myCat

    包名

    全小写

    package

    com.animal

    常量

    全大写(用下划线隔开)

    final

    PI,MY_CONST

    除了上述驼峰法标准写法之外,还可以用下滑线法,如 my_class,my_cat


    //方法名在有不同参数的情况下可以使用同一个方法名,即有参数和没参数的方法可以同名
    class Person{ //请注意类名首写字母应为大写如Person为类名 
    	int age;
    	String name;
    	//1、可以输出我是好人方法
    	public void speak(){  //请注意方法名的首写字母应为小写如speak为方法名
    		System.out.println("我是一个好人");
    	}
    	//2、可以计算1+..+1000的方法
    	public void jiSuan(){
    		int result=0;
    		for(int i=1;i<=1000;i++){
    			result=result+i;
    		}
    	System.out.println("1+..+1000结果是"+result);
    	}
    	//3、带参数的成员方法,可以输入n值并计算1+..+n
    	public void jiSuan(int n){
    		int result=0;
    		for(int i=1;i<=n;i++){
    			result+=i;
    		}
    		System.out.println("1+..+n结果是"+result);
    	}
    	//4、计算两个数的和
    	public void add(int num1,int num2){
    		int result=0;	//与下面一句等同于return num1+num2;
    		result=num1+num2;
    		System.out.println("num1+num2="+result);
    	}
    	//5、计算两个数的和,并将结果返回给主调(调用它的)方法
    	//注意:返回类型和返回结果的类型要一致
    	//注意:在调用某个成员方法的时候,给出的具体数值的个数
    	//和类型要相匹配。
    	public int add2(int num1,int num2){
    		return num1+num2;
    	}
    	//6、计算两个float数的和,并将结果返给主调方法
    	public float add3(int num1,float num2,float num3){
    		return num1+num2+num3;
    	}
    }

    类的成员方法--声明

    public int test(int a);/*方法声明*/

    这句话的作用是声明该方法,声明的格式为:

    访问修饰符 数据类型 方法名(参数列表);

    在给Person类添加add方法的例子中,关键字return的功能是把表达式的值返回的值返回给主调方法

    	return 表达式;

    类的成员方法(函数)--特别说明

    1、方法的参数列表可以是多个

    参数列表可以是多个,并且数据类型可以是任意的类型int float double char..

    访问修饰符 返回数据类型 方法名(参数列表){
    		语句; //方法主体
    	}
    2、方法可以没有返回值

    返回类型可以是任意的数据类型(int,float,double,char..)也可以没有返回值void表示没有返回值

    访问修饰符 返回数据类型 方法名(形参列表){
    		语句; //方法主体
    }

    2、构造方法(函数)

    类的构造方法介绍

    构造方法是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:

    1、方法名和类名相同

    2、没有返回值

    3、在创建一个类的新对象时,系统会自动的调用该类的构造方法完成对新对象的初始化。

    特别说明:

    一个类可以定义多个不同的构造方法。

    //定义一个人类
    class Person{
    	int age;
    	String name;
    	//自定义默认构造方法
    	public Person(){
    	}
    	//构造方法的主要用处是:初始化你的成员属性(变量)
    	//自定义带参数构造方法
    	public Person(int age,String name){
    		age=age;
    		name=name;
    	}
    	//构造方法2
    	public Person(String name){
    		name=name;
    	}
    }

    类的默认构造方法

    如果没有定义构造方法,系统会自动生成一个默认构造方法。当创建Person对象时Person per1=new Person();默认的构造方法就会被自动调用。

     

    类的构造方法小结:

    1、构造方法名和类名相同;

    2、构造方法没有返回值;

    3、主要作用是完成对新对象的初始化;

    4、在创建新对象时,系统自动的调用该类的构造方法;

    5、一个类可以有多个构造方法;

    6、每个类都有一个默认的构造方法。

     

    3、this关键字

    this是属于一个对象,不属于类的。java虚拟机会给每个对象分配this,代表当前对象。

    注意事项:this不能在类定义的外部使用,只能在类定义的方法中使用

     

    /*	
    	this的必要性
    */
    public class Test{
    	public static void main(String []args){
    		Dog dog1=new Dog(2,"大黄");
    		Person p1=new Person(dog1,23,"张三");
    		Person p2=new Person(dog1,24,"李四");
    		p1.showInfo();	
    		p1.dog.showInfo();
    	}
    }
    //定义一个人类
    class Person{
    	//成员变量
    	int age;
    	String name;
    	Dog dog;//引用类型
    	public Person(Dog dog,int age,String name){
    		//可读性不好
    		//age=age;
    		//name=name;
    		this.age=age; //this.age指this代词指定是成员变量age
    		this.name=name; //this.name指this代词指定是成员变量name
    		this.dog=dog;
    	}
    	//显示人名字
    	public void showInfo(){
    		System.out.println("人名是:"+this.name);
    	}
    }
    
    class Dog{
    	int age;
    	String name;
    	public Dog(int age,String name){
    		this.age=age;
    		this.name=name;
    	}
    	//显示狗名
    	public void showInfo(){
    		System.out.println("狗名叫"+this.name);
    	}
    }

    4、类变量、类方法

    类变量

    类变量是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

     

    定义类变量:

    访问修饰符 static 数据类型 变量名;

    例如Person类中的

    public static int num;

     

    访问类变量

    类名.类变量名  或者  对象名.类变量名

    例如

    Person.num;

    或者

    Person person = new Person();
    person.num;


    类方法

    类方法是属于所有对象实例的,其形式如下:

    	访问修饰符 static 数据返回类型 方法名(){}

    注意:类方法中不能访问非静态变量(类变量)。

    使用:类名.类方法名 或者 对象名.类方法名

    *重点*static静态的方法可以访问static静态变量(类变量),不能访问非静态变量非静态方法可以访问非静态变量同时也可以访问static静态变量(类变量)。


    public class Test{
    	public static void main(String []args){
    		//创建学生对象
    		Stu stu1=new Stu(29,"aa",340);
    		Stu stu2=new Stu(29,"aa",240);
    		System.out.println(Stu.getTotalFee());
    	}
    }
    
    //学生类
    class Stu{
    	int age;
    	String name;
    	int fee;
    	static int totalFee;
    	public Stu(int age,String name,int fee){
    		this.age=age;
    		this.name=name;
    		totalFee+=fee;
    	}
    	//返回总学费[这是一个类方法(静态方法)]
    	//java中规则:类变量原则上用类方法去访问或操作
    	public static int getTotalFee(){
    		return totalFee;
    	}
    }

    类变量小结

    1、当某一属性是所有对象所公有(共用)的情况用类变量

    2、类变量与实例变量区别:

    加上static称为类变量或静态变量,否则称为实例变量

    类变量是与类相关的,公共的属性

    实例变量属于每个对象个体的属性

    类变量可以通过 [类名.类变量名]  直接访问

     

    类方法小结

    1、当某一方法只使用或不使用静态变量(类变量)的情况用类方法

    类方法属于与类相关的,公共的方法

    实例方法属于每个对象个体的方法

    类方法可以通过 [类名.类方法名]  直接访问


    5、java面向对象编程的四大特征(抽象/封装/继承/多态)

    抽象

    把一类事物的共有的属性和行为提取出来,形成一个模型(模版)。这种研究问题的方法称为抽象。

     

     

    封装

    封装就是把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。

     

    封装--访问控制修饰符

    java提供四种访问控制修饰符号控制方法和变量的访问权限:

    1、公开级别:用public修饰,对外公开

    2、受保护级别:用protected修饰,对子类和同一个包中的类公开

    3、默认级别:没有修饰符号,向同一个包的类公开

    4、私有级别:用private修饰,只有类本身可以访问,不对外公开

    4种访问级别的访问范围

    访问级别

    访问控制修饰符

    同类

    同包

    子类

    不同包

    公  开

    public

    受保护

    protected

    默  认

    没有修饰符

    私  有

    private

     

    包--必要性

    包--三大作用

    1、区分相同名字的类

    2、当类很多时,可以很好的管理类

    3、控制访问范围

     

    包--导包命令

    package com.自定义名字;

    注意:包命令一般放在文件开始处。

     

    包--命名规范

    小写字母  比如 com.test

     

    包--常用的包

    一个包下,包含很多的类,java中常用的包有:

    包名

    作用

    java.lang

    默认导入提供了Java语言进行程序设计的基础类

    java.util

    提供了包含集合框架、日期和时间各种实用工具类

    java.io

    通过文件系统、数据流和序列化提供系统的输入与输出

    java.net

    提供实现网络应用与开发的类。

    java.sql

    提供了使用Java语言访问并处理存储在数据库中的数据API

    java.awt

    提供了创建界面和绘制图形图像的所有类

    javax.swing

    提供了一组轻量级的组件

    java.text

    提供了与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。

     

      上表中的这些包,除了第一个包是自动导入外,其余的包都需要使用import语句导入,才可使用其包里面的类与接口。

     

    包--如何引入包

    语法:import 包;

    比如import java.awt.*;

    引入一个包的主要目的要使用该包下的类。

     

     

    继承

    继承--基础知识

    继承可以解决代码复用,让编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类:

    语法:class 子类 extends 父类

    这样,子类就会自动拥有父类定义的某些属性和方法

     

    继承--深入讨论

    1、

    父类

    子类

    public 属性

    继承

    protected 属性

    无修饰符属性

    public 方法

    protected 方法

    无修饰符方法

    private 属性

    不继承

    private 方法

     

    2、结论

    从表可以看出,父类的public修饰符的属性和方法;protected修饰符的属性和方法;默认修饰符属性和方法被子类继承了,父类的private修饰符的属性和方法不能被子类继承。

     

    继承--注意事项

    1、子类最多只能继承一个父类(指直接继承)

    2、java所有类都是Object类的子类 (所有的子类都可以逐级继承,例:爷->父->子->孙)

     

     

    多态

    方法重载(overload)和方法覆盖(override)

    在实现多态前,必须先弄明白方法重载(overload)和方法覆盖(override)。

     

    方法重载(overload)

    方法重载就是类的同一种功能的多种实现方式,到底采用哪种方式,取决于调用者给出的参数。

    注意事项:

    1、方法名相同

    2、方法的参数类型,个数,顺序至少有一项不同

    3、方法返回类型可以不同(只是返回类型不一样,不能构成重载)

    4、方法的修饰符可以不同(只是控制访问修饰符不同,不能构成重载)


    //方法重载(overload)getMax
    public class Test{
    	public static void main(String []args){
    		MyMath mymath=new MyMath();
    		System.out.println(mymath.getMax(12,14));
    		System.out.println(mymath.getMax(24f,20f));
    	}
    }
    
    class MyMath2{
    	//返回较大的整数
    	public int getMax(int i,int j){
    		if(i>j){
    			return i;
    		}else{
    			return j;
    		 }
    	}
    	public float getMax(float a,float b){
    		if(a>b){
    			return a;
    		}else{
    			return b;
    		 }
    	}
    }

    方法覆盖(override)

    方法覆盖就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,这就是子类的这个方法覆盖了父类的那个方法。

    注意事项:

    方法覆盖有如下条件

    1、子类的方法的返回类型,参数,方法名称,要和父类的返回类型,参数,方法名称完全一样,否则编译出错。

    2、子类方法不能缩小父类方法的访问权限。


    //子类方法覆盖父类方法
    public class Test{
    	public static void main(String []args){
    		//创建一只猫
    		Cat cat1=new Cat();
    		cat1.cry();
    		Dog dog1=new Dog();
    		dog1.cry();
    	}
    }
    //动物类
    class Animal{
    	int age;
    	String name;
    	//都会叫
    	public void cry(){
    		System.out.println("Animal not cry");
    	}
    
    }
    //猫类
    class Cat extends Animal{
    	//覆盖父类方法
    	public void cry(){
    		System.out.println("Cat cry");
    	}
    }
    //狗类
    class Dog extends Animal{
    	//覆盖父类方法
    	public void cry(){
    		System.out.println("Dog cry");
    	}
    }

    多态--概念

    多态是指一个引用(类型)在不同情况下的多种状态。也可以理解成:多态是指通过指向父类的指针,来调用在不同子类中实现的方法。

    实现多态有两种方式:1、继承;2、接口

     

    public class Test{
    	public static void main(String[] args) {
    		//多态
    		Animal an=new Cat();
    		an.cry();
    		an=new Dog();
    		an.cry();
    	}
    }
    //动物类
    class Animal{
    	String name;
    	int age;
    	//动物会叫
    	public void cry(){
    		System.out.println("Animal not cry");
    	}
    }
    //创建Dog子类并继承Animal父类及覆盖cry方法
    class Dog extends Animal{
    	//狗叫
    	public void cry(){
    		System.out.println("Dog cry");
    	}
    }
    class Cat extends Animal{
    	//猫自己叫
    	public void cry(){
    		System.out.println("Cat cry");
    	}
    }

    多态--注意事项:

    1、java允许父类的引用变量引用它的子类的实例(对象)

    Animal an=new Cat();//这种转换时自动完成的

    2、如果需要访问子类中父类没有的数据,需要强制转换为子类

    Animal an=new Cat();//这种转换时自动完成的
    Cat cat = (Cat)an;		//这是需要手动设置的

    多态--补充

    继承是多态得以实现的基础。从字面上理解,多态就是一种对象引用(类型)表现出多种状态。将一个方法调用同这个方法所属的主体(也就是对象或类)关联起来叫做绑定,分前期绑定和后期绑定两种。下面解释一下它们的定义:

    1、前期绑定:在程序运行之前进行绑定,由编译器和连接程序实现,又叫做静态绑定。比如static方法和final方法,注意,这里也包括private方法,因为它是隐式final的。

    2、后期绑定:在运行时根据对象的类型进行绑定,由方法调用机制实现,因此又叫做动态绑定,或者运行时绑定。除了前期绑定外的所有方法都属于后期绑定。

     

    多态就是在后期绑定这种机制上实现的。多态给我们带来的好处是消除了类之间的偶合关系,使程序更容易扩展。

     

    6、抽象类

    当类的一些方法不能确定时,可以用abstract关键字来修饰该方法[抽象方法],用abstract来修饰该类[抽象类]。

    public class Test {
    	public static void main(String[] args) {
    		//Animal an=new Animal();抽象类不允许实例化
    		Animal an=new Cat();
    		an.cry();
    		an=new Dog();
    		an.cry();
    	}
    }
    //抽象类abstract关键词
    abstract class Animal{
    	String name;
    	int age;
    	//动物会叫,使用了abstract抽象方法
    	abstract public void cry();//抽象类中可以没有abstract抽象方法
    	//抽象类内可以有实现方法
    	public void sx(){
    		System.out.println("实现方法");
    	}
    }
    //当一个子类继承的父类是abstract抽象类的话,需要把抽象类的抽象方法全部实现。
    class Cat extends Animal{
    	//实现父类的cry
    	public void cry(){
    		System.out.println("Cat cry");
    	}
    }
    class Dog extends Animal{
    	//实现父类的cry
    	public void cry(){
    		System.out.println("Dog cry");
    	}
    }

    抽象类--深入讨论

    1、用abstract关键字来修饰一个类时,这个类就是抽象类。

    2、用abstract关键字来修饰一个方法时,这个方法就是抽象方法。

    3、abstract抽象类中的abstract抽象方法是不允许在抽象类中实现的,一旦实现就不是抽象方法和抽象类了。abstract抽象方法只能在子类中实现。

    4、抽象类中可以拥有实现方法。

     

    抽象类--注意事项

    1、抽象类不能被实例化

    2、抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract抽象方法。

    3、一旦类包含了abstract抽象方法,则这个类必须声明为abstract抽象类。

    4、抽象方法不能有主体。

    正确的抽象方法例:abstract void abc();

    错误的抽象方法例:abstract void abc(){} 


    7、接口

    接口就是给出一些没有内容的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。

    接口建立语法:

    interface 接口名{方法;}

    接口实现语法:

    class 类名 implements 接口{
    		方法;
    		变量;
    }


    小结:

    接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体。接口体现了程序设计的多态和高内聚低偶合的设计思想。

     

    接口--注意事项

    1、接口不能被实例化

    2、接口中所有的方法都不能有主体。错误语法例:void aaa(){}接口可以看作更加抽象的抽象类。

    3、一个类可以实现多个接口

    4、接口中可以有变量[但变量不能用private和protected修饰]

       a、接口中的变量,本质上都是static的而且是final类型的,不管你加不加static修饰

       b、在java开发中,经常把常用的变量,定义在接口中,作为全局变量使用

    访问形式:接口名.变量名

    5、一个接口不能继承其它的类,但是可以继承别的接口而且可以多继承

    6、当一个类实现了一个接口,要求该类把这个接口的所有方法全部实现


    //USB接口
    interface Usb{
    public void start();
    public void stop();
    }
    class Camera implements Usb{
    	public void start(){
    		System.out.println("我是相机,开始工作了..");
    	}
    	public void stop(){
    		System.out.println("我是相机,停止工作了..");
    	}
    }
    //编写了一个手机,并实现了usb接口
    class Phone implements Usb{
    	public void start(){
    		System.out.println("我是手机,开始工作了..");
    	}
    	public void stop(){
    		System.out.println("我是手机,停止工作了..");
    	}
    }
    //计算机
    class Computer{
    	//开始使用usb接口
    	public void useUsb(Usb usb){
    		usb.start();
    		usb.stop();
    	}
    }
    public class Test{
    	public static void main(String[] args) {
    		//创建 Computer
    		Computer computer=new Computer();
    		//创建Camera
    		Camera camera1=new Camera();
    		//创建Phone
    		Phone phone1=new Phone();
    		computer.useUsb(camera1);
    		computer.useUsb(phone1);
    	}
    }

    实现接口VS继承类

    java的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比C++中的多继承机制简洁。但是不可否认,对子类功能的扩展有一定影响。

    所以:

    1、实现接口可以看作是对继承的一种补充。(继承是层级式的,不太灵活。修改某个类就会打破继承的平衡,而接口就没有这样的麻烦,因为它只针对实现接口的类才起作用)

    2、实现接口可在不打破继承关系的前提下,对某个类功能扩展,非常灵活。


    8、final关键字

    final基本概念

    1、final中文意思:最后的,最终的。

    2、final可以修饰变量或者方法。

    3、被final修饰的变量其数据就不能改变了,就成为了常量(const)。

     

    在某些情况下,可能有以下需求:

    1、当不希望父类的某个方法被子类覆盖(override)时,可以用final关键字修饰。

    2、当不希望类的某个变量的值被修改,可以用final修饰。如果一个变量是final,则必须赋初值,否则编译出错。

    3、当不希望类被继承时,可以用final修饰。

     

    //final方法的使用
    public class Test{
    	public static void main(String[] args) {
    		Aaa aaa=new Aaa();
    		aaa.show();
    		Bbb bbb=new Bbb();
    		bbb.show();
    	}
    }
    class Aaa{ 
    //如果a不赋初值,a是0。定义类型后应赋值
    	int a=0;	
    //圆周率不让修改
    	//带有final修饰的变量命名时应有_下划线来区分表示并且单词应该是全大写。
    //需要强制不被修改的数据一定要用final锁定
    	final float REATE_1=3.1415926f;
    
    //使用final定义变量时一定要赋初值否则报错。
    	//final int b;  
    	//b=1;
    	final public void sendMes(){//给成员方法用final来修饰则表示不可以被修改,不可被覆盖。
    		System.out.println("发送消息");
    	}
    	public void show(){
    		System.out.println("a="+a);
    	}
    }
    final class Bbb extends Aaa{//定义类前加final表示该类不允许被继承
    	public Bbb(){
    		a++;
    //不可以被修改
    		//REATE_1+=1;
    	}
    	/*不能被覆盖
    public void sendMes(){
    		System.out.println("发送消息")
    	}*/
    }

    final--注意事项

    1、final修饰的变量又叫常量,一般用XX_XX_XX(大写)来命名。(带下划线)

    2、final修饰的变量在定义时,必须赋值,并且以后不能再赋值。

     

    final--什么时候用

    1、类的某个方法不允许修改。

    2、类不被其它的类继承。

    3、某些变量值是固定不变的,比如圆周率3.1415926

     

     

    9、补充

    初始化代码块与静态初始化代码块


    public class Test{
    	static int i=1;
    	static{
    		//该静态初始块只被执行一次
    		i++;
    		System.out.println("执行一次");
    	}
    	public Test(){  
    		System.out.println("执行二次");
    		i++;
    	}
    {
    //该初始块每次创建对象被执行一次
    i++;
    		System.out.println("执行二次");
    
    }
    	public static void main(String []args){
    		Test t1=new Test();  
    		System.out.println(t1.i);
    
    		Test t2=new Test();
    		System.out.println(t2.i);
    	}
    }



    结论:

    1、静态代码块只会在类加载进内存时,被执行一次。

    2、代码块会在每次创建对象被执行一次。

    3、代码块的访问顺序是从上至下,依次访问。



    ----------参考《韩顺平.循序渐进学.java.从入门到精通》

    ----------参考《JDK_API_1_6_zh_CN

    Java学习笔记--导航http://blog.csdn.net/q547550831/article/details/49819641


    展开全文
  • Java面向对象总结篇

    千次阅读 多人点赞 2019-01-17 15:48:33
    面向对象在百度百科中是这样解释的:“面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物”。说的好像很流弊的样子,看看就行。 说道面向对象,大家肯定会想到面向对象的三大基本...

    Java面向对象总结

    小白今天来整理一下Java面向对象,顺带回顾一下Java基础,虽然小白也没啥基础,但是还是写一点吧,毕竟发布一篇原创博客还有10个积分/xieyanxiao

    什么是面向对象?

    面向对象在百度百科中是这样解释的:“面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物”。说的好像很流弊的样子,看看就行。

    说道面向对象,大家肯定会想到面向对象的三大基本特征:封装、继承、多态
    在这里插入图片描述
    下面小白就从这三大基本特征来说一下Java的面向对象。

    Java 封装

    在面向对象设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
    ① 封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。
    ② 封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
    封装的优点:
    1、封装能够减少耦合性
    2、类内部的结构可以自由修改
    3、可以对成员变量进行控制
    4、隐藏信息,实现细节
    下面小白举个例子来实现一下封装的步骤吧:
    在这里插入图片描述
    如上图所示:将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。

    Java 继承

    继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
    继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
    在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,下面我们通过一个例子来说一下继承的优点吧,如下:
    在这里插入图片描述
    在这里插入图片描述
    从这两段代码可以看出来,代码存在重复了,导致后果就是代码量大且臃肿,而且维护性不高,所以要从根本上解决这两段代码的问题,就需要继承,将两段代码中相同的部分提取出来组成 一个父类,如下:
    在这里插入图片描述
    这个Animal类就可以作为一个父类,然后猪类和狗类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:
    在这里插入图片描述
    在这里插入图片描述
    需要注意的是 Java 不支持多继承,但支持多重继承
    什么意思呢,小白简单来解释一下,
    public class A {

    }
    public class B extends A{

    } //这是单继承

    public class A {

    }
    public class B extends A{

    }
    public class C extends B{

    }//这是多重继承

    public class A {

    }
    public class B extends A{

    }
    public class C extends A{

    }//这是不同的类继承同一个类
    下面这种写法是不正确
    public class A {

    }
    public class B {

    }
    public class C extends A,B{

    }//JAVA不支持多继承

    继承的特性

    1、子类拥有父类非 private 的属性、方法
    2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
    3、子类可以用自己的方式实现父类的方法
    4、提高了类之间的耦合性

    继承的缺点

    耦合度高就会造成代码之间的联系越紧密,代码独立性越差

    Java多态

    多态是同一个行为具有多个不同表现形式或形态的能力。同一个接口,使用不同的实例而执行不
    同操作。例如:
    一台打印机,他有打印功能,但是打印的具体内容不同,彩色打印机打印的效果是彩色的,黑白打印机打印的是黑白的。

    多态的优点

    1、消除类型之间的耦合关系
    2、可替换性
    3、可扩充性
    4、接口性
    5、灵活性
    6、简化性

    多态的三个必要条件

    继承、重写、父类引用指向子类对象,例如:
    Animal dog = new Dog();
    当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
    以下是一个多态实例的演示,详细说明请看注释:

    public class Test {
        public static void main(String[] args) {
          show(new Cat());  // 以 Cat 对象调用 show 方法
          show(new Dog());  // 以 Dog 对象调用 show 方法
                    
          Animal a = new Cat();  // 向上转型  
          a.eat();               // 调用的是 Cat 的 eat
          Cat c = (Cat)a;        // 向下转型  
          c.work();        // 调用的是 Cat 的 work
      }  
                
        public static void show(Animal a)  {
          a.eat();  
            // 类型判断
            if (a instanceof Cat)  {  // 猫做的事情 
                Cat c = (Cat)a;  
                c.work();  
            } else if (a instanceof Dog) { // 狗做的事情 
                Dog c = (Dog)a;  
                c.work();  
            }  
        }  
    }
     
    abstract class Animal {  
        abstract void eat();  
    }  
      
    class Cat extends Animal {  
        public void eat() {  
            System.out.println("吃鱼");  
        }  
        public void work() {  
            System.out.println("抓老鼠");  
        }  
    }  
      
    class Dog extends Animal {  
        public void eat() {  
            System.out.println("吃骨头");  
        }  
        public void work() {  
            System.out.println("看家");  
        }  
    }
    

    输出结果为:
    吃鱼
    抓老鼠
    吃骨头
    看家
    吃鱼
    抓老鼠

    下面顺带说总结一下抽象类和接口

    java 抽象类

    什么是抽象类抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样
    由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用
    举个例子,下面给大家简单的介绍一下抽象类:

    public abstract class Students
    {
       private String name;
       private String address;
       public Employee(String name, String address)
       {
          System.out.println("构造方法");
          this.name = name;
          this.address = address;
       }
       public void speak()
       {
          System.out.println("我叫 " + this.name
           + "家住" + this.address);
       }
       public String toString()
       {
          return name + " " + address;
       }
       public String getName()
       {
          return name;
       }
          public void settName(String newName)
       {
          name = newName;
       }
       public String getAddress()
       {
          return address;
       }
       public void setAddress(String newAddress)
       {
          address = newAddress;
       }
    }
    //大家应该注意到了,抽象类除了类前面的修饰符不一样,其他的和普通类没啥区别。 
     在Java语言中使用abstract class来定义抽象类
     
    public class Test
    {
       public static void main(String [] args)
       {
          /* 以下是不允许的,会引发错误 */
          Students e = new Students("张三", "召唤师峡谷");
          System.out.println("**************");
          e.speak();
        }
    }
    //这样写法是不正确的,当你编译Test类的时候,程序会报错
    Students is abstract; cannot be instantiated....
    

    所以,抽象类不能实例化对象,但是我们可以继承抽象类。如下:

    public class Person extends Students
    {
       private int age; 
       public Person (String name, String address, int age)
       {
           super(name, address);
           setAge(age);
       }
       public void speak()
       {
            System.out.println("我叫" + this.name + "家住" + this.address+ "年龄" +this.age);
       }
        public int getAge()
        {
          return age;
        }
        public void setAge(int age)
        {
           if(age>0)
           {
              age= age;
           }
         }
    }
    
    //虽然我们不能实例化Students类,但是我们可以实例化Person类,
    public class Test
    {
       public static void main(String [] args)
       {
          Person p = new Person ("张三", "召唤师峡谷蓝方", 23);
          Students s = new Person ("李四", "召唤师峡谷红方", 24);
          p.speak();
          s.speak();
        }
    }
    输出结果为:
    我叫张三家住召唤师峡谷蓝方年龄23
    我叫李四家住召唤师峡谷红方年龄24
    

    抽象方法

    如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
    Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
    抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。例如:

    public abstract class TestStudents
    {
       private String name;
       private String address;
       private int age;
       
       public abstract String play();//抽象方法
    }
    
    //声明抽象方法会造成以下两个结果:
    1、如果一个类包含抽象方法,那么该类必须是抽象类。
    2、任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
    另外还有一点需要注意一下:抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
    

    Java 接口 (特殊的抽象类)

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

    接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

    除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

    接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

    接口与类的相似点

    1、一个接口可以有多个方法
    2、接口文件保存在 .java 结尾的文件中,文件名使用接口名。
    ……

    接口与类的区别

    1、接口不能用于实例化对
    2、接口没有构造方法
    3、接口中所有的方法必须是抽象方法
    4、接口不能包含成员变量,除了 static 和 final 变量
    5、接口不是被类继承了,而是要被类实现
    6、接口支持多继承

    接口的特性:

    1、接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
    2、接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
    3、接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

    抽象类和接口的区别

    1、 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
    2、抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    3.、接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
    4、 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
    下面我们来定义一个接口看一下:

    interface Animal {
       public void eat();
       public void paly();
    }
    

    接口的实现

    类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
    例如:

    public class Dog implements Animal{
     
       public void eat(){
          System.out.println("dog eats");
       }
     
       public void paly(){
          System.out.println("dog play");
       } 
       public static void main(String args[]){
          Dog d = new Dog();
          d.eat();
          d.play();
       }
    }
    

    以上就是小白整理的关于Java面向对象方面的知识点,就简单的整理了一下,其中还有或多或少有不足之处,还望看到的大佬们指点一下/tuosai


    业精于勤,荒于嬉---------自带小尾巴

    展开全文
  • Android NDK编译C/C++so共享对象

    万次阅读 2017-03-18 21:31:56
    介绍了NDK、JNI、.so共享对象的概念,以及通过NDK将C/C++编译.so的流程。
  • 面向对象分析与设计——对象模型

    千次阅读 2014-10-22 09:18:58
    对象模型  对象模型包括:抽象、封装、模块化、层次结构、类型、并发和持久   2.1 对象模型的演进   OO建立在以前技术的最佳思想之上。两大趋势:小规模→大规模;高级程序设计语言;   2.1.1 程序...
  • PHP面向对象编程详解:类和对象

    千次阅读 2015-06-17 07:40:24
    PHP面向对象编程详解:类和对象 从OOP的视角看,不应区分语言。无论是C++、无论是Java、无论是.net还有更多面向对象的语言,只要你了解了OO的真谛,便可以跨越语言,让你的思想轻松的跳跃。便没有对于Java...
  • 面向对象编程的思想

    万次阅读 热门讨论 2020-09-12 16:12:00
    面向对象思想 前言        计算机的革命起源于机器,所以说,编程语言的产生也是始于对机器的模仿。在19世纪的早期就已经出现过计算机的雏形。那时,人们为了构建导航所需的表格对...
  • 巧用Android网络通信技术,在网络上直接传输对象

    万次阅读 多人点赞 2013-05-25 07:57:01
    那么我们来看一下,一般Android应用程序里都是怎么实现网络交互的,这里拿一个Boook对象为例,首先在手机端生成一个Book对象,里面包含书名、作者、价格等数据。为了要将这些数据发送到服务器端,我们要从Book对象中...
  • 01 PHP面向对象基础

    千次阅读 2020-04-09 23:25:26
    面向对象基础 学习目标:理解面向对象编程思想,了解计算机编程语言的演变过程,掌握PHP面向对象的基础语法,使用面向对象编程思想和面向对象语法实现编程解决需求问题 计算机语言发展史 面向过程编程思想 面向...
  • Java实现面向对象编程

    万次阅读 2018-07-17 16:18:06
    1.1.1什么使用面向对象... 14 1.1.2使用面向对象进行设计... 15 1.2通过创建对象实现领养宠物功能... 17 1.2.1创建类的对象... 17 1.2.2构造方法及其重载... 23 1.2.3常见错误... 28 1.3使用封装优化电子...
  • 面向对象编程思想

    万次阅读 2014-06-24 21:18:46
    并在系统构造中尽可能运用人类的自然思维方式,强调直接以问题域(现实世界)中的事物中心来思考问题,认识问题,并根据这些事物的本质特点,把它们抽象地表示系统中的对象,作为系统的基本构成单位(而不是用
  • 分布式对象技术

    千次阅读 2007-12-15 21:18:00
    以面向对象技术主要特征的分布式计算技术从90年代初开始,经过多年的发展,现已经进入了成熟时期,一般将其称为分布式对象技术。分布式对象技术主要是在分布式异构环境下建立应用系统框架和对象构件,在此框架的...
  • 因为想在笔记中夹杂这篇文章中部分内容,所以进行了还是比较多的引用,核心的地方还是自己写的 可以一直接调转到深入理解Java的OOP ...名称:Object-Oriented Analysis:面向对象分析方法 OOA是指...
  • C#之连接数据库:Connection对象

    千次阅读 2015-05-19 20:41:00
    ADO.NET简介  ADO.NET是微软提供的一个通用的框架类库,该类库将跨越所有存在的Windows API... ADO.NET是以Open Database Connectivity(ODBC)应用程序技术的使用标志的数据库访问技术发展起来最新的一项技术。ADO
  • 对C++面向对象的编程的理解

    千次阅读 2019-01-16 18:05:06
    面向对象方法历经了30多年的研究和发展,已经日益成熟和完善,应用也越来越深入和广泛,现其已经发展为主流的软件开发方法。 本节主要介绍面向对象方法的优点以及它的一些基本概念。 2.3.1面向对象方法的优点(1)与...
  • 面向对象的理解(一)

    千次阅读 2018-06-11 09:59:53
    C++是在C语言的基础上进行扩充和完善发展而来的,它兼容了C语言的面向过程特点,又成为了一种面向对象的程序设计语言。而JAVA是一门完全面向对象的语言。因此对于学过C++想转战JAVA的同学,首先就得弄清面向对象这个...
  • C++类与对象详解

    千次阅读 2015-06-04 20:52:11
    类和对象作为面向对象中重要部分,本文详细的介绍其中的内存管理、继承、虚函数与多态、拷贝构造函数等基本原理,使大家对其原理有一个系统的认识
  • 面向对象的分析方法

    千次阅读 2015-05-11 21:55:16
    是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题。OOA与结构化分析有较大的区别。OOA所强调的是在系统调查资料的基础上,针对OO方法所需要的素材进行的归类分析和整理,而不是对管理...
  • .net版本发展历史

    千次阅读 2015-09-17 10:10:24
    .net版本发展历史 最近装上了VS2013,发现好多新特性、新功能,公司办公还在使用VS2005、VS2008,不过用着也很顺手,在最新版Visual Studio中,微软加入了git源码管理工具,和之前的TFS大体上类似。.net发展的现在...
  • 什么是面向对象编程

    万次阅读 多人点赞 2020-01-12 18:57:20
    本文关键字:面向对象、面向过程、面向过程编程语言、面向对象编程语言。说到编程,对于初学者来讲,可能第一想到的就是敲键盘,写代码,做游戏,甚至于会联想到软件破解、网络攻防。另一方面,在学了一些编程的相关...
  • Java对象创建与垃圾收集器实现

    千次阅读 2017-10-06 08:08:16
    Java是一门面向对象的编程语言,在Java程序运行过程中,无时无刻都有对象被创建出来;当对象没被引用时,Java垃圾收集器此时发挥作用,回收没有用的对象,释放对应所占的内存。 一、Java对象创建 在语言层面上,...
  • 面向对象复习总结

    千次阅读 2016-12-07 21:11:27
    面向对象思想的发展使得在软件开发中使用模式化的方法受到了重视,模式化的思想来源于建筑业。 建筑大师Alexander: 每一个模式描述了一个在我们周围不断重复发生的问题以及该问题解决方案的核心,这样你就可以一...
  • Go语言面向对象编程

    千次阅读 2014-01-26 09:31:31
    前段时间接触Go语言,感觉有很多新的理念,今天先转载一篇文章,以后...Go 语言的面向对象编程(OOP)非常简洁而优雅。说它简洁,简介之处在于,它没有了OOP中很多概念,比如:继承、虚函数、构造函数和析构函数、隐藏的
  • 而面向对象方法提出后,当代软件工程的发展面临着从传统的结构化范型到面向对象范型的转变,对象技术作为新范型的核心技术正在得到新的语言、新的系统和新的方法学的支持,这体现在已出现的面向对象的语言、数据库、...
  • 面向对象系统分析与设计

    千次阅读 2016-05-03 13:25:32
    数据抽象  把一组数据及作用与其上的操作组成一个设计实体或单位。 接口说明外部可见,其实现是...2.从以程序中心到以信息内容(数据)中心、或以用户中心 3.从孤立封闭的集中控制式系统到协同开发的分布式系统
  • 在传统的软件开发过程中多采用结构化技术完成软件开发的一系列工作,但这种方法并不适应发型软件产品的开发,而随着硬件发展,对于大型软件产品的需求又逐步提高,于是面向对象方法就应运而生。面向对象方法在上世纪...
  • 控制系统中被控对象建模

    千次阅读 2020-05-21 14:45:01
    在控制理论发展的早期,被控对象模型是通过分析对象的组成结构,结合相关的物理与数学知识,建立起系统输出与输入之间的解析关系式(这种建模方法又称为机理建模)。由于这种方法具有很强的物理意义,因此可以较为...
  • 面向对象程序设计

    千次阅读 2011-10-23 22:26:22
    面向对象程序设计 求助编辑百科名片 面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象...
  • 如果我们不学习,科技发展的速度会让我们五年后被清空。所以,我们必须用初学者谦虚的自觉,饥饿者渴望的求知态度,来拥抱未来的知识。 这几天做的项目中需要从图库选择图片或者拍照生成图片,然后展现在IamgeView...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,879
精华内容 62,351
关键字:

如何确定为发展对象