前言:本文章对于java的入门学习基于有C/C++以及python基础,部分重叠的基础知识可能有大量删减


Java简述

跳过简述|直接莽干

简单易学

Java最初是为对家用电器进行集成控制而设计的一种语言,因此它必须简单明了。

Java语言的简单性主要体现在四个方面:

​ 1、Java的风格类似于C++,从某种意义上讲,Java语言是C及C++语言的一个变种因而C++程序员初次接触Java语言,就会感到很熟悉。

​ 2、Java摒弃了C/C++中容易引发程序错误并且难以掌握的一些特性,如指针、结构、以及内存管理等。

​ 3、Java提供了丰富的类库,可以帮助我们很方便的开发Java程序。

安全性高

​ 1、java是一种强类型的语言,其类型检查比C/C++还要严格。类型检查帮助我们检查出许多开发早期出现的错误

​ 2、java提供了垃圾回收机制,有效的避免了C/C++中最头疼的内存泄漏问题

​ 3、java禁止非法内存访问,在没有授权的情况下是不能访问内存的.所有这些措施,使Java程序员不用再担心内存的崩溃

跨平台

Java作为一种网络语言,其源代码被编译成一种结构中立的中间文件格式。

只要有Java运行系统的机器都能执行这种中间代码。Java源程序被编译成一种与机器无关的字节码格式,在Java虚拟机上运行。

多线程

从略

快速开始

新建文本文件,命名为QuickStart.java,通过创建同名的 ,并调用System类中的outprintln方法输出内容。

程序运行接口与C类似的,是main()函数.

1
2
3
4
5
6
//QuickStart.java
public class QuickSrart{
public static void main(String[] args){
System.out.println("hello world\n");
}
}

基本数据类型与运算

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

基本数据类型占字节数占位数最小值最大值包装类型默认值
boolean1字节8位Booleanfalse
byte1字节8位-128127Byte0
char2字节16位\u0000\uffffCharacter\u0000
short2字节16位-2^152^15-1Short0
int4字节32位-2^312^31-1Integer0
float4字节32位Float0.0f
long8字节64位-2^632^63-1Long0L
double8字节64位Double0.0d
  • 与C++不同,Java中的布尔值boolean,而不是bool,可用%b%B输出。
  • 与C不同,输出时:整数都是%d,浮点数都是%f,没有%lf等概念。
  • 值得注意的是,java中,char型数据占两个字节,编码不是ASCII码而是 Unicode码

类型转换

数据类型的转换遵循以下规则:

  1. 不能对boolean进行类型转换

  2. 不能把类型转换成其它对象类型

  3. 把容量大的类型转换成容量小的类型,需通过比较 进行强制转换

  4. 强制类型转换可能会损失精度

$ 注:byte < char < int < long < float < double $

运算符

以下介绍均以展现异同为主,不过多介绍

1
2
3
4
5
6
7
8
9
//加法运算
class ADD{
public static void main(String[] args){
System.out.println(3+4);//结果为 7
System.out.println('a'+1);//结果为 97+1=98 'a'的Unicode是97
System.out.println(""+'a'+1);//结果为 a1
System.out.println("hello "+"world!");//结果为 hello world!
}
}
  • JAVA中的加法在C的基础上扩充了许多功能,诸如字符串拼接等。(这与C++和python类似)

1
2
3
4
//取余运算
double x = 1%-0.3;
System.out.println(x);
//结果为:0.10000000000000003
  • JAVA中,取余运算符的右边可以为浮点数,而/则与C中一致,若两边均为整型结果取整。

    但是python中,/默认结果是浮点数,//才表示整除含义


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//位运算符

int a = -9;// a = 11110111(二进制补码)
a = a >> 1;
/* a右移一位:
(a是负数,最高位补1)
1 1111011
(开始时的最后那个1因为右移舍去)
*/

int b = -9;
b = b >>> 1;
/* b右移一位:
(无论b是否为负,最左边一律补0)
0 1111011
(开始时的最后那个1因为右移舍去)
*/

// << 和 <<< 同理
  • 与C不同,C 只存在>><<,实际效果因编译器而异;但java运用两种形式做了统一

流程控制

if语句、for循环、while循环、switch语句,break、continue、return用法……

与C一致(笑)

待补充

面向对象

类的创建

java 和 C++与python “如出一辙”,我们通过class这个关键字,创建一个类。

1
2
3
4
class GirlFriend{
int height;
int weight;
}

而在主函数中,通过这个 类 来创建一个对象:

1
2
3
4
public static void main(String[] args){
GirlFriend jane = new FirlFriend();
//我们俗称的,new一个对象 QAQ
}
  • 当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值

与C\+\+不同的最根本的原因是JAVA的语法规则。

我们知道,new就是在 中动态的分配一个空间,以上面的例子来看,也就是相当于将堆中的这块内存的地址传给 在 中 静态分配的jane,用C的话来说,此时jane就是指向这个对象的一个 指针

1
2
3
4
5
6
7
8
9
10
11
12
GirlFriend* jane = new FirlFriend();
GirlFriend* jane = (FirlFriend*)malloc(sizeof(GirlFriend));
/*
在C++中,上述两个语句几乎等同。
不过如果想要用 jane 进行操作(访问成员),需要使用:
jane->height = 165;

在java中,隐藏了指针这个概念(取消了*号),
而且用户可以直接通过如下方式操作:
jane.weight = 100;

*/

总而言之,在java中,我们可以简单的把jane就理解为一个GirlFriend对象,并对其操作。(但实际上二者并不等同,中间通过指针操作)

理解这一点后,我们再来看下面这段代码:

1
2
3
4
5
6
7
8
9
10
GirlFriend jane = new GirlFriend();
GirlFriend lily = new GirlFriend();
GirlFriend robin = jane;// line1

jane.height = 165;
jane.weight = 100;//line2
lily.height = 190;

System.out.printf("%d,%d,%d,%d\n",jane.height,lily.height,robin.height,robin.height);
//结果为:165,190,165,100

重点看看我们标line1的那一行代码,如果我们简单的理解为 “重新创建一个新对象,对象中的所有属性值都与jane对象一模一样,二者是两样东西”,那就错了。

我们发现,在line2中,我们对jane的体重进行设置,最后输出robin的体重,却还是和jane一样。

这说明,我们的好兄弟找的女朋友并不是jane的双胞胎,而是jane换了一个名字robin去勾搭我们的好兄弟!

janerobin这两个变量代表的,其实是同一个对象。

如果用C中指针的想法来思考,那就是这两个指针都指向同一个内容。


访问控制符

访问控制符一定程度上可以保证程序的安全性,灵活使用可以使得类的外部不能轻易使用 私有属性和方法

示例:

1
2
3
4
5
6
7
8
9
10
11
12
class GirlFriend{
public int height;
public int weight;
private int bust;
private int waist;
private int hip;
protected String boyfriend_name;
}
/*
在主函数中,不能直接访问bust、waist、hip,否则报错
但 height、weight、boufriend_name可以访问
*/

类的访问控制符有四种:

  • publicprivateprotected

  • 默认【即不加任何修饰符default

在一个类的内部,所有的成员可以相互访问,访问控制符是透明的;访问控制符是针对外部而言的

外部访问包括两种方式:

  • 通过类名访问类内部的成员
  • 通过类对象名访问类内部成员

这与C++是完全一致的,具体可以参考C++的学习笔记

既然protected外部也可以访问,那么这与public有什么区别呢?

这在之后 继承 的学习中,会着重提及~

传送门:继承/包 限制表???


构造函数

在python的学习中,我们可以对类进行初始化操作:

1
2
3
4
5
class Person :
def __init__(self,name):
self.name = name

p1 = Person("jane")

简单理解,就是我们在 “主函数”中,创建一个类对象时,会有一个函数自动执行,如果需要参数,就在new的时候传递。

就如python中的__init__()函数。

当然,作为面向对象的编程语言,C++也有 构造函数 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person{
public:
void show_name(void);
int get_age(void);
Person();//构造函数,与类同名
private:
int age;
string name;
};

//成员函数定义,包括构造函数
Person::Person(int ipt_age,string ipt_name){
age = ipt_age;
name = ipt_name;
}
void Person::show_name(void){
cout << name << endl;
}
int Person::get_age(void){
return age;
}
  • 不难发现,C++中的许多内容,包括访问控制符都在java中得到体现,所以 构造函数也同样得到继承

在Java中,构造函数同样需要和类名相同,不需传参时,也不必像python一样需要加入self

继续以之前的女朋友为例,编写可初始化身高和体重属性的构造函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class GirlFriend{
int height;
int weight;
private int bust;
private int waist;
private int hip;
protected String boyfriend_name;
//构造函数
public GirlFriend(int h,int w){
height = h;
weight = w;
}
}

//启动类
public class QuickStart {
public static void main(String[] args){
GirlFriend jane = new GirlFriend(165,100);
GirlFriend lily = new GirlFriend(190,98);
}
}

规则总结:

  1. “构造函数”不能有 “返回值”
  2. 函数名 必须 与 类名 一致

事实上,在我们没有自己编写的构造函数之前,其实本身内部就相当于已经有一个无参数的空函数了:

1
public GrilFriend(void){}

但是,如果我们编写了一个构造函数之后,原来的空函数被替换掉了。

再一个问题,我们能否写多个不同的构造函数?

1
2
3
4
5
6
7
8
public GrilFriend(void){
System.out.println("无参函数~~~~");
}
public GrilFriend(int a){
System.out.println("有一个参数的函数~~~~");
}
public GrilFriend(int a,int b){
System.out.println("有两个参数的函数~~~~");

其实是可以的,这涉及到 函数重载 问题,事实上这是与C++一模一样的,具体可见:C++快速入门


this指针

在说明this指针之前,我们先来看一段C语言代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include<stdlib.h>
struct A{
int i;
};
void show_i(struct A* THIS){
printf("%d\n",THIS->i);
}
int main(void){
struct A *aa = (struct A*)malloc(sizeof(struct A));
aa->i = 5;
show_i(aa);
}
  • 本代码中,show_i()函数的作用是:将数据类型为struct A的指针所指向的i成员的数值打印出来
  • 其中我们用到了一个形参变量:THIS,即本质上,THIS是一个指针变量,从而将我们传给他的那个指针所指向的i找出来

在Java中,设计一个类模板之后,在“主函数”里创建多个示例时,其“方法成员”是存储在代码区/方法区,且只需储存一个的。就类似于上面的show_i()一样,只定义这一个,但是我却可以创给它不同的指针,它就给出不同的结果。

如:

1
2
3
4
5
6
7
8
9
class A{
private int name;
public A(int j){
name = j; //line3
}
public void show_name(){ //line 1
System.out.println("name = " + name); //line 2
}
}

我们在“主函数”里,用不同实例如aa1,aa2来调用A.show_name()时,打印结果分别是aa1,aa2的不同的name

那是因为,实际上在line1这一行中,show_name()的完整思路是show_name(A* this)

然后line2里面是println("name = " + this->name)。不过这些已经被java隐去了,程序员可以不去在意这背后的逻辑关系。


但是一般的情况下中,善用this->是更加符合代码规范的。

就如上面的println("name = " + this.name)一样,因此line3的语句也常常用下面的代码代替:this.name = j;


A:为什么line2的规范写法不是用this->name,而是this.name ?

Q:这在我们前面的阐述中其实已经提到过了传送门,java隐去了指针的概念,所以与指针关联的两个符号:*->也没有了。


这其实就类似于我们python中的self关键词。

现在我们来回看一下前面讲 “构造函数” 的时候,我们搬出来的python的例子:

1
2
3
4
5
class Person :
def __init__(self,name):
self.name = name #line4

p1 = Person("jane")

可见,line4与前面的line3几乎是一样的。那么我可不可以像python一样,不再用j传递,也用名为name的形参进行传递呢?

答案是:可以。

因此,我们java的构造函数也能得到类似的代码:

1
2
3
4
5
6
class A{
private int name;
public A(int name){
this.name = name; //二者并不冲突;当然,如果用 name = name就会报错
}
}

关于static

在前面女朋友的例子中,我们利用指针的知识系统的阐述了janerobin指向的是同一个对象。因此,如果对jane的身高进行修改,robin也会相应的改变(毕竟就是同一个东西)。

事实上,也确实可以将某一个属性设置为 静态变量,可将其视为 类本身的属性,而不只是对象的属性,属于公共的。这需要用到关键词static.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//文件名:M.java

class A{
public int i;
public static int j = 10;
private static int k = 9;
public static void show(){
System.out.println("hello world!\n");
System.out.printf("%d、%d\n",j,k);//静态能够访问静态
System.out.printf("%d\n",i);//报错:静态不能访问非静态
}
public void f(){
show();//非静态能够访问静态
}

}
class M{
public static void main(String[] args){
System.out.println(A.i);//报错
System.out.println(A.j);//输出:10
A aa1 = new A();
A aa2 = new A();
aa1.j = 99;
System.out.println(aa2.j);//输出:99
A.show();//不报错,输出:hello world!
A.k = 1;//报错
}
}

【重要】总结

  1. 多个对象可共用static属性
  2. 静态属性可以通过类名直接访问
  3. 想要访问静态属性还需该属性是非私有的(即:不被private控制符修饰)
  4. 静态方法不能访问非静态属性,但是非静态能访问静态
    • 解释:因为在没有创建对象的情况下,不存在非静态的属性,因此无法访问
  5. static不能用来修饰构造函数
  6. 静态方法不能使用thissuper(super会在之后的讲解中出现)

static的应用

【1】造轮子计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A{
private static int count = 0;//计数变量
public int id;//id属性
public A(){//无参构造函数
count++;
}
public A(int id){
this.id = id;
count++;
}
/*
由于每new一个对象就会执行构造函数,
所以可以通过静态的count变量记录new的次数
*/
public static int getCount(){
return count;//定义函数返回对象个数
}
}

【2】出品限量款

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A{
public int id;
//id属性

private static A aa = new A();
//创建私有静态属性aa,同时aa也是一个A对象

private A(){
/*
如果构造函数为private,
则主函数中 A aa = new A()报错
而上面之所以能new是因为private在A类内部是透明的
*/
}

public static A getOneA(){
return aa;
}
/*
创建公用static函数使得主函数能够获得一个A对象,
而且由于aa是static,所以创建成功后有且仅指向唯一一个
*/

}
public class M{
public static void main(String[] args){
A aa1 = new A();//error
A aa2 = A.aa;//error
A aa3 = A.getOneA();//succeed
A aa4 = A.getOneA();//succeed

/*
但是 aa3 与 aa4 指向的是同一个对象
即 aa3 == aa4
*/
}
}
  • getOneA()必须是static,因为主函数无法new对象(因为构造函数是private),所以在主函数需要用类名来调用此函数
  • 属性aa必须是static,否则getOneA()静态函数不能访问非静态成员,而且由于aa是static,所以创建成功后有且仅指向唯一一个,满足我们 只能创建一个对象 这样的需求

继承

之前我们的女朋友示例中,我们定义了许多属性,如:身高、体重、三围、男朋友的名字等等。我们发现,除了男朋友的名字这一项以外,其他的都是一个人类也都具备的属性。

事实上,为了代码的可读性和完善性,我们希望事先创建一个人类这样的类,然后再创建一个女朋友的类,然后女朋友这个类也有人类该有的一些属性。为此,继承这一要素成了必要。

简介

一个新类从已有的类那里获得其已有的属性和方法,这就叫类的继承。
这个新类被称为子类,也叫派生类,已有的那个类叫做父类,也叫做基类

继承的好处

  • 代码得到极大的重用
  • 形成一种类的层次体系结构
  • 为多态创造条件

如何继承

1
2
3
4
5
6
7
8
9
10
11
12
class SuperClass{
public int i;
protected int j;
private int k;
/*省略*/
}
class SubClass extends SuperClass{
/*
内部已经存在有 i,j了~
*/
/*省略*/
}
  • superclass表示父类,subclass表示子类。通过extend来实现继承

注意事项

  1. 子类不能够继承私有(private)属性和方法

    • 事实上私有属性也被继承过来了,可以通过我们接下来的super语句进行初始化。
    • 但是普通的逻辑上语法上是无法直接在子类里面访问的。因此继承必须慎重,否则会浪费内存
  2. 继承/包 限制 表

作用域当前类同一 package子孙类其他 package
public
protected×
friendly××
private×××
  1. Java只支持 单继承,不支持多继承
    • C++可以通过虚继承实现多继承,而Java可以通过 接口 一定程度上解决多继承的部分需求
  2. 子类不能继承父类的构造方法
    • 可以通过super部分解决

子类构造函数

我们知道,构造函数可以很方便的为类成员进行初始化

1
2
3
4
5
6
7
8
9
class A{
public int i;
public int j;
public A(){}
public A(int i,int j){
this.i = i;
this.j = j;
}
}

而当有一个新的类继承上面的类之后,其本身也会继承有相应的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
class B extends A{
public int k;
public B(){}
public B(int k){
this.k = k;
}
}

/*
主函数
B bb = new B();
System.out.println(bb.i); 不会报错
*/

但是想要也对继承过来的属性进行初始化就得

1
2
3
4
5
public B(int i,int j,int k){
this.i = i;
this.j = j;
this.k = k;
}

如果父类的属性较多,子类想要初始化属性的话,写构造函数也会十分繁琐。

在C++中,我们可以通过以下方式解决:

1
2
3
4
5
/* B 的构造函数 */
B::B(int i,int j,int k){
A(i,j);//调用A的构造函数完成i,j的初始化
this->k = k;
}

不过这并不能在java中适用,因此Java引出了super的概念,只需在初始化B的时候调用super()函数,就完成了“调用父类A的构造函数完成初始化”这一行为:

1
2
3
4
public B(int i,int j,int k){
super(i,j);
this.k = k;
}

但是,值得注意的是 :

  1. super必须是在父类有构造函数的前提下,在子类的构造函数中调用,且必须是第一个执行语句

  2. 如果我们不在子类的构造函数中主动写 super语句,系统默认为其添加一个super()语句[super空]。

    • 如果此时父类中没有无参版的构造函数,程序仍然报错
  3. 子类的构造函数中,能且只能使用一次super()函数

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//test1
class B extends A{
public int j;
public B(){
//super();//系统默认添加
}
}

//test2
class B extends A{
public int j;
public B(int i,int j){
super();
super(i);//error
}
}

//test3
class B extends A{
public int j;
public B(int i,int j){
this.j = j;
super(i);//error
}
}

重写方法

有些时候,子类不一定要用到和父类一模一样的方法。这种时候我们可以对子类继承过来的方法进行“重写”:

1
2
3
4
5
6
7
8
9
10
11
class A{
public void f(){
System.out.println("AAAA\n");
}
}
class B extends A{
//重写
public void f(){
System.out.println("BBBB\n");
}
}

B中的f()方法和A的同返回值同形参列表,但是结果不再一样。

但是,重写也得有一定的规则限制:

  1. 重写方法必须和被重写方法具有相同的方法名称、参数列表和返回类型
  2. 子类中不允许出现与父类同名同参但不同返回类型的方法,如果出现,编译时会报错
  3. 覆盖方法时,不能使用比父类中被覆盖的方法更严格的访问权限(原因我们会在多态的学习中再讨论)
父类使用子类可用
publicpublic
protectedpublic、protected
private不被继承

事实上,重写之后,我们还可以用回A的方法:

1
2
3
4
5
6
7
class B extends A{
//重写
public void f(){
super.f();//通过super代指父类,调用父类的f()方法
System.out.println("BBBB\n");
}
}

实例展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//M.java

class Person{
private int id;
private String name;
public Person(int id,String name){
this.id = id;
this.name = name;
}
public String getname(){
return this.name;
}
public int getid(){
return this.id;
}
public String show_inf(){
String inf = "information:"+id+" "+name;
return inf;
}
}
class Student extends Person{
private int score;
//构造函数
public Student(int id,String name,int score){
super(id, name);//直接this.id = id 报错,因为父类中id是private
this.score = score;
}
//方法重写
public String show_inf(){
String inf = super.show_inf()+" "+this.score;
/*
String inf = "information:"+id+" "+name+" "+this.score 报错
可等效为:
String inf = "information:"+super.getid()+" "+super.getname()+" "+score;
或:
String inf = "information:"+this.getid()+" "+this.getname()+" "+score;
*/
return inf;
}
}
public class M{
public static void main(String[] args){
Student std = new Student(190,"zhangsan",100);
System.out.println(std.get_inf());
}
}

多态

参考资料

1.java入门教程|郝斌

2.java基本数据类型字典表

3.C++类构造函数&析构函数

4.Java访问权限符表格|CSDN