我爱编程

Java Basic 1

2017-10-23  本文已影响0人  andrew7

key point

Java和C语言比较

  1. Java面向对象、C面向过程
  2. Java通过字节码实现可移植性、C要重新编译
  3. Java为运行时提供了全面的检测程序 Java Mission Control
  4. Java 没有指针,也没有指针相等运算
double *pd = (double*)malloc(sizeof(int) * 12);
//我们申请了24字节的空间 这个空间的起始地址存放在pd中
  1. Java提供了垃圾自动回收的机制
  2. Java没有结构体,无法从底层布局内存
stract student{
    char* name;
    int number;
};
  1. Java没有预处理器,C在预处理的过程中会替换宏定义
#define MAXLENGTH 100   

Java和C++语言之间的比较

  1. Java的对象模型要比C++的简单
  2. Java默认使用虚分派
  3. Java始终使用值传递
  4. Java不支持多继承
  5. Java泛型没有C++的模板强大
  6. Java无法重载运算符
#include<iostream>
#include<string>

class Student
{
    private:
        std::string name;
        int age;
    public:
        Student(std::string name, int age){
            this->name = name;
            this->age = age;
        }
        Student(std::string name){
            Student(name,0);
        }
        Student(int age){
            Student(NULL, age);
        }
        ~Student(){
            
        } 
        std::string getName(){
            return name;
        }
        void setName(std::string name){
            this->name = name;
        }
        int getAge(){
            return age;
        }
        void setAge(int age){
            this->age = age;
        }   
};

同样的一个类用Java定义就是如此

public class Student {
    private String name;
    private Integer age;
    
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void getAge(Integer age){
        this.age = age;
    }
    public Integer setAge(Integer age){
        return age;
    }  
}
public class Greeting {
    String intro = "Hello";
    String target(){
        return "world";
    }
}
public class FrenchGreeting extends Greeting{
    String intro = "Bonjour";
    String target(){
        return "le monde";
    }

    public static void main(String [] args){
        Greeting english = new Greeting();
        Greeting french = new FrenchGreeting();

        System.out.println(english.intro + "," + english.target());
        System.out.println(french.intro + "," + french.target());
        System.out.println(((FrenchGreeting)french).intro + "," +  ((FrenchGreeting)french).target());
    }
}

以上程序输出的结果是
Hello,world
Hello,le monde
Bonjour,le monde

virtual dispatch 机制会首先从 receiver(被调用方法的对象)的类的实现中查找对应的方法,如果没找到,则去父类查找,直到找到函数并实现调用,而不是依赖于引用的类型。

什么是Java引用

简单的说,引用其实就像是一个对象的名字或者别名 (alias),一个对象在内存中会请求一块空间来保存数据,根据对象的大小,它可能需要占用的空间大小也不等。访问对象的时候,我们不会直接是访问对象在内存中的数据,而是通过引用去访问。引用也是一种数据类型,我们可以把它想象为类似 C 语言中指针的东西,它指示了对象在内存中的地址——只不过我们不能够观察到这个地址究竟是什么。

如果我们定义了不止一个引用指向同一个对象,那么这些引用是不相同的,因为引用也是一种数据类型,需要一定的内存空间来保存。但是它们的值是相同的,都指示同一个对象在内存的中位置。

//Test.java
public class Test {  
    public static void test(StringBuffer str) {  
        str.append(", World!");  
    }  
    public static void main(String[] args) {  
        StringBuffer string = newStringBuffer("Hello");  
        test(string);  
        System.out.println(string);  
    }  
} 
//最后输出的结果是Hello,World!
//Test.java 
public class Test {  
    public static void test(String str) {  
        str = "World";  
    }  
    public static void main(String[] args) {  
        String string = "Hello";  
        test(string);  
        System.out.println(string);  
    }  
}  
//最后输出的结果是hello。

通过上述的例子 可以发现Java是传值的,在上面的例子中 我们改变了str 这个引用或者别名的值,最终string的值并没有发生变化

Java和php语言之间的比较

  1. Java是静态语言 php是动态语言
  2. Java是通用语言 PHP在网站之外的很少见
  3. Java支持多线程 PHP不支持

Java和Javascprit之间的比较

  1. Java是静态语言 Javascript是动态语言
  2. Java是基于类的对象 Javascript是基于原型的对象
  3. Java提供了良好的封装 Javascript没有提供
  4. Java有命名空间
  5. Java支持多线程

Java程序

Java程序由一个或者多个Java源码文件组成。
每个编译单元都以可选的package声明组成。
后面跟着0个或者多个import声明。这些声明制定一个命名空间。
再可选的package和import之后是0个或者多个引用类型定义。(他们往往都是class或者interface)

package

java的6种语言结构

  1. 访问对象成员
  2. [] 访问数组中的元素
  3. ()调用方法
  4. lambda表达式(->)
  5. 创建对象new
  6. ()类型转换或者校正

Java方法的签名包含以下内容

变长参数

在方法最后一个参数的类型后面加上省略号,指明最后一个参数可以重复0次或者多次

 public static void max(int ... rest){
        for (int each:
             rest) {
            System.out.println(each);
        }
    }

Java的基本类型

字符类型 char
布尔类型 boolen
数值类型 byte short int long float double
其他 void

Java的5种引用类型

类 数组 接口 枚举 注解

Student student = new Student() 的表述

申明一个存储Student的对象的引用(reference) 这个引用的名称叫做student

对象字面常量

类似于int 至于Integer, double 至于Double

  1. 字符串字面量
    String name = "David";
    2.类型字面量
    Class<?> typeInteger = Integer.class;
  2. null 引用

Java 的一个类

类的定义

类有一些保存值的数据字段和处理这些值的方法组成

对象

对象是类的实例

一个类的定义包括一个签名和一个主体。
签名 定义类的名称
签名可能会声明自己扩展自其他类。被扩展的类成为超类 被扩展的为子类。
子类继承超类的成员 同时可以声明新成员。
主体 包含放在花括号里面的成员
成员

类的成员可以使用访问修饰符 public、protected、private来指定使用方和子类中能否被访问、是否可见。

类的签名可能会声明自己实现了一个或者多个接口。

public class Cirecle{
  private static Double pi = 3.14;//类字段
  privare double r; //实例字段
  public double area(){
    return 
  }//事例方法
  public static double radisToDegrees(double radians){
    return radians * 180 /PI;
  }//类方法
}

接口

Java引用类型中的一种,其中定义了方法签名,但是没有实现

public interface IRead{
    void read(String content){
        System.out.println(content);
    }
}

Abstract 关键字

abstract 修饰的类未完全实现 不能实例化。

抽象类和接口之间的区别

面向对象特性封装 继承 多态

面向对象特性面向对象特性

封装
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

在Java中类中成员的属性有:public, protected, default(不加任何修饰符), private,这四个属性的访问权限依次降低。

同一个类 同一个包 不同包的子类 不同包的非子类
Private
Default
Protected
Public

继承
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”。

被继承的类称为“基类”、“父类”或“超类”。

继承的过程,就是从一般到特殊的过程。

要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。

继承概念的实现方式有三类:实现继承、接口继承和可视继承。

Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;

Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

Ø 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

抽象类仅定义将由子类创建的一般属性和方法,创建抽象类时,请使用关键字 Interface 而不是 Class。

OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

实现多态,有二种方式,覆盖,重载。

覆盖,是指子类重新定义父类的虚函数的做法。

重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚邦定,它就不是多态。”

那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

抽象类和接口之间的区别

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口相当于方法的集合
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

构造方法

public class Student {
    private String name = "Tony";
    private Integer age = 18;

    public Student(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    public Student(String name){
        this(name,null);
    }
    public Student(Integer age){
        this(null, age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

实例字段初始化

类中的字段不一定需要初始化 如果没有初始值,字段自动使用默认值初始化 false \u0000 0 0.0 null

字段声明不是任何方法的一部分。JDK 会为字段声明生成初始化方法,然后把这些代码放到所有类的构造方法中

类字段初始化

类字段要在调用构造方法之前初始化,为此javac汇给每一个类自动生成一个 类初始化方法 这个方法只在首次使用类之前调用一次(经常是Java虚拟机首次加载类的时候) 。 在类文件中,它的名称是 client ,这个方法对Java程序员不可见。

类初始化程序

不过Java允许编写用于初始化类字段的代码,所用的结构叫做静态初始化程序

public class TrigCircle{
    private static int final NUMPTS = 500;
    private static double sines[] = new double[NUMPTS];
    private static double cosines[] = new double[NUMPTS];
    static {
        double x = 0.0;
        double delta_x = (Cirecle.PI/2)/(NUMBER-1);
        for(int i = 0 , x = 0.0; i < NUMPTS; i++, x+= delta_x){
        sines[i] = Math.sin(x);
        cosines[i] = Math.cos(x);
        }
    }
}

对象初始化程序

相对应的还有对象初始化程序,只不过初始化的是对象而不是类。和类初始化程序之间的差别是不加static。

类初始化对象 和 类初始化对象

他们在类对象和对象对象初始化之前执行

子类和继承

类的扩展

构造方法链和默认构造方法

创建类的实例的时候,Java保证一定会调用这个类的构造方法,创建任何子类的实例,Java保证一定会调用超类的构造方法。如果一个类继承与其他的类,并且超类不存在传参个数为0个构造方法。那么这种隐式调用会导致编译错误。

public class Circle{
  private final double r;
  public Circle(double r){
      this.r =  r;
  }
  public double getCircle(){
      return r;
  }
  public double setCircle(double r){
      this.r = r;
  }
}
public class PlaneCirecle extends Cirecle{
  private final double cx, cy;
  public PlaneCirecle(double x, double y, double r){
      super(r);
      this.x = x;
      this.y = y;
  }
  //get and set etc...
  public boolean isInside(double x, double y){
      double dx = x - cx;
      double dy = y - cy;
      double l = Math.sqrt(dx*dx + dy* dy);
      return l < r;
  }
}
PlaneCircle pc = new PlaneCirle(1.0, 0.0, 0.0);
//无需校正,赋值给Circle类型的变量
Circle c = pc;
//缩小需要校正(虚拟机需要做运行时检查)
PlaneCircle p = (PlaneCircle)c;

PlaneCircle 的构造顺序

覆盖超类的字段

比如说我们在PlaneCircle定义了一个r

public double r;

在这种情况就覆盖了超类的r 如果要调用r的方法的化需要使用super关键子

super.r //如果r是能够访问的话

或者

((Circle)this).r //引用Circle类的字段

现在有类 A B C C是B的子类 B是A 的子类,他们之中都定义了一个名称为x的字段。

x // C中的x
this.x //C中的x
((B)this).r //B中的x
((A)this).r //A中的x

以上是实例字段被遮盖的情况下的使用
对于类对象也可以使用这种方法,但是也可以直接把类名称放到类字段名称之前,因为类字段是属于类的 而不是属于某个特定的对象的

覆盖超类的方法

覆盖和遮盖之间的差别

为了引用遮盖的字段,只需要把对象校正成适当超类的实例,但不能使用这种方式调用覆盖的实例方法。

public class A {
    int i = 1;
    int f(){
        return i;
    }
    static char g(){
        return 'A';
    }
}
public class B extends A{
    int i = 2;
    int f(){
        return -i;
    }
    static char g(){
        return 'B';
    }
}
public class Test {
    public static void main(String args[]){
        B b = new B();
        System.out.println(b.i);
        System.out.println(b.f());
        System.out.println(b.g());
        System.out.println(B.g());

        A a = (A) b;
        System.out.println(a.i);
        System.out.println(a.f());
        System.out.println(a.g());
        System.out.println(a.g());
    }
}

输出结果
2
-2
B
B
1
-2
A
A

如果想使用被覆盖的方法的话 要使用super关键字
使用super引用被遮盖的字段的时候,相当于把this修正为超类类型,然后通过超类类型访问字段
解释器使用super句法调用实例方法的时候,会执行一种修改过的虚拟方法查找。
第一步确定调用这个方法的类属于那个类,正常情况下会调用这个类对应的方法。
但是使用suprer句法调用方法时,先在这个类的超类里面查找。如果超类直接实现了这个方法 那就调用超类的,
如果超类继承了这个方法 那就调用继承的方法。
注意 super 调用的是方法的直接覆盖版本。如果有类 A、B、C,三个类当中都有speak方法,并且B是A的子类,C是B的子类。那么我们在C中使用super.spake中调用的是B中的speak方法。C中是无法不能直接调用A中的speak方法的。

权限控制

  • 子类继承超类中所有可以访问的示例字段和实例方法
  • 如果子类和超类在同一个包内定义,那么子类继承所有没有使用private声明的实例字段和方法
  • 如果子类在其他包中定义,那么它继承所有使用protected和public申明的实例字段和方法
  • 使用private声明的字段和方法绝对不会被继承;
  • 构造方法不会被继承
    注意 这里的继承并不是不会被他分配内存空间

转化引用类型

对象可以在不同的引用类型转化。这个转化可以是放大(编译器自动完成) 也可以是缩小(运行时检查)。

类层次检查

每个Java引用类型都扩展自其他类型。所有类都直接或者间接的扩展自根类Object。
引用类型的转换规则

接口

凌型继承

棱形继承棱形继承

打个比方说 baseClass 有一个属性 叫做name 那么 通过多继承后 derivedClass 的内存记录里面就有两个名称为name的属性。我们在derivedClass中会为超类baseClass的属性分配两次地址。增加调用的困难,同时也会浪费内存资源。

#include<stdio.h>
#include<iostream>
#include<queue>
using namespace std;

class A {
public:
    A(){printf("A create.\n");}
    int a;
    virtual void fun(){}
};

class B: public A{
public:
    B(){printf("B create.\n");}
    int b;
    virtual void fun1(){}
};

class C: public A
{
public :
    int c;
    C(){printf("C create.\n");}
    virtual void fun3(){printf("fun3 .\n");}
};

class D:public C,public B{
public:
    int d;
    D(){printf("D create.\n");}
    virtual void fun3(){printf("fun4 .\n");}
}; 
//二义性问题的开销 
int main() {
    D *pd=new D;
    printf("%d\n",sizeof(D));
    getchar();
}

造成二义性问题,需要指定作用域

    D *pd=new D;
    pd->B::a=1;
    pd->C::a=2;
    printf("%d\n",pd->B::a);
    printf("%d\n",pd->C::a);

cpp 通过虚继承来解决问题

#include<stdio.h>
#include<iostream>
#include<queue>
using namespace std;

class A {
public:
    A(){printf("A create.\n");}
    int a;
    virtual void fun(){}
};

class B: public A{
public:
    B(){printf("B create.\n");}
    int b;
    virtual void fun1(){}
};

class C: public A
{
public :
    int c;
    C(){printf("C create.\n");}
    virtual void fun3(){printf("fun3 .\n");}
};

class D:public C,public B{
public:
    int d;
    D(){printf("D create.\n");}
    virtual void fun3(){printf("fun4 .\n");}
}; 
enter description hereenter description here

对于baseClass是公用的,也就是baseClass就实例化了一个对象!想想这会有什么后果?调用B,C的虚函数的时候就一个虚表怎么行,所以有需要对应有两个相应的虚表指向B,C,于是就成了上面的结构了。

Java不支持多继承

基于上面的原因Java不支持多继承 但是支持实现多重接口,这里我们不是扩展(继续)什么类,而是实现一个类,在其中添加一些属性和行为。这不是从父类中直接获得一些行为和属性。
而实现接口 是 I have
继承是 I am

接口成员

扩展接口

接口的定义可以包含

编译时类型和运行时类型

编译时类型和运行时类型

多态:编译时类型表现出运行时类型的行为(虚函数表)

设计模式六大原则

单一职责原则

(当对一个类进行修改后,另外一个类的功能不受影响)

里氏替换原则

I 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
II 子类可以增加自己的特有方法
III 子类的方法重载父类的方法时,方法的前置条件要比父类的输入参数更加宽松
IIII 当子类实现父类的抽象方法时,方法的后置条件要比父类严格)

依赖倒置原则

class Book{
    public String getContent(){
        return ("once upon the time");
    }
}
class Mother{
    public void narrate(Book book){
        System.out.println(book.getContent());
    }
}
//但是如果有一个NewsPaper 类 那就没有办法了
class NewsPaper{
    public String getContent(){
        return("Scientist discorved that ...");
    }
}
//所以正确的应该这么做
public interface IReader{
    public String getContent();
}
public Book implements IReader{
    public String getContent(){
        return "once upon the time...";
    }
}
public Mother{
    public void narrate(IReader iReader){
        return System.out.print(iReader.getContex)
    }
}

之所以要这么做是因为 可以降低类之间的耦合

接口隔离原则

interface I {
    public void method1();
    public void method2();
    public void method3();
    public void method4();
    public void method5();
}

public A implements I {
    public void method1(){
        System.out.println("需要实现");
    }
    public void method2(){
        System.out.printlon("需要实现")
    }
    //虽然不需要 但是还是要实现
    //servlet 就是这样的接口
    public void method3(){
    }
    public void method4(){
    }
    public void method5(){
    }
    //最好的方法就是 将这个接口分割成若干的接口

迪米特法则

class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

class SubEmployee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

class CompanyManager{
    public List getAllEmployee(){
        List list = ArrayList();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            emp.setId("总公司员工编号" + i);
            list.add(emp);
        }
        return list;
    }
    //在该处发生了不必要的耦合
    public void printAllEmployee(SubCompanyManager sub){
    List list = sub.getAllEmployee();
    for(SubEmployee e:list){
        System.out.print(e.getId);
    }
    
    List list1 = this.getAllEmployee();
    for(Employee e: list1){
        System.out.print(e.getId());
    }
}

开闭原则

对拓展开放 对修改关闭

总结

单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

上一篇下一篇

猜你喜欢

热点阅读