地址

Java简介

Java应用程序的运行机制:

  • Java是编译型和解释型的结合

  • 流程:

    1. Java首先利用文本编辑器编写Java源程序,源文件的后缀名为.java
    2. 再利用,编译器(javac)将源程序编译成字节码文件,字节码文件的后缀名为.class
    3. 最后利用虚拟机(解释器,java)解释执行。
  • JVM(Java Virtual Machine)

    就是一个虚拟的用于执行字节码文件的“虚拟计算机”

  • Java Runtime Environment(JRE)

    包含:Java虚拟机库函数运行Java应用程序所必须的文件。

  • Java Development Kit(JDK)

    包含:包含JRE,以及增加编译器调试器等用于程序开发的文件。

  • 三者关系:

1567849870375

基础

1
2
3
4
5
public class hello1{
public static void main(String[] args){
System.out.println("hello world");
}
}
  • 一个源文件中至多只能声明一个public的类
  • 原文件名必须和其中定义的public的类名相同,且以“.java”为扩展名。
  • 正确编译后的源文件,会得到相应的字节码文件,编译器为每个类生成独立的字节码文件
  • main方法是Java应用程序的入口方法

变量

1
2
3
4
5
6
7
8
9
10
11
12
public class hello1{
int a = 1; /*成员变量 , 从属于对象*/
static b = 2; /*静态变量 , 从属于类*/
final num = 10; // 常量,一旦被初始化后不能再更改其值
public static void main(String[] args){
{
int c = 5; /*局部变量 , 从属于语句块*/
}
System.out.println(a);

}
}

3类8种基本数据类型

  • 数值型: byte,short,int,long,float,double
  • 字符型: char
  • 布尔型: boolean

数据类型

1
2
3
4
int a = 15;   /*十进制*/
int b = 015; /*以0开头为八进制*/
int c = 0x15; /*以0x开头为16进制*/
int d = 0b1101; /*以0b开头为2进制*/
  • 整形的默认类型是int,所以如果要用long,就必须在后面添加L
  • 浮点型的默认类型是double , 如果要用float,就必须在后面添加F
1
2
3
4
5
6
7
8
9
10
public class hello3{
public static void main(String[] args){
int a = 2000000;
/*和python2一样,只要数字大于int,long类型必须在数字的后面添加L*/
long b = 10000000000000L;
/*同理,单精度浮点型要使用F作为结尾*/
float c = 1.23F;
System.out.println(015);
}
}

浮点型是不精确的,所以不能比较。或者可以使用java.math包的类:BigInteger和BigDecima 进行比较。

字符串与字符 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class hello1{
public static void main(String[] args){
char a = 'a';
char b = 'b';
System.out.println(a+b); /*195*/
System.out.println(""+a+b); /*ab*/
}
}

public class hello2{
public static void main(String[] args){
String a = "abc";
String b = "def";
System.out.println(a+b); /*abcdef*/
}
}

数据类型的转化

建议运算的数据类型要一致。

和python不一样,python跨类型运算时,只是转化结果的类型

  • X = 1.5+4
  • 1.5和4都保持原先的float类型和int类型,其结果X升级为float类型

Java是数据自身转变

  • int X =byte 1 + int 2
  • byte 1 自己变成int 1
  • 也就是说,没有long时,结果为int。即使操作数全为short,byte,结果也是int

数据类型的强制转化

1
2
3
4
目标数据类型变量 = (目标数据类型) (被转化的数据)

eg :
(int)b
1
2
3
4
5
6
7
8
public class hello2{
public static void main(String[] args){
byte a = 3;
int b = 4;
byte c = (byte) (a+b);
System.out.println(c); /*7*/
}
}

虽然在Java的变量运算中数据类型升级和python不一样。但是Java常量运算的数据类型升级和python是一样的:

  • 变量运算,先提升类型再说。
  • 常量运算,先算一下结果,再判断可不可以。

原因:变量只能在运行的时候确定他的值,常量在编译的时候就可以确定值了。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class hello3{
public static void main(String[] args){
byte a = 2;
byte b = 1;
/*变量运算,先提升类型再说。*/
byte c = (byte) (a+b);
/*常量运算,先算一下结果,再判断可不可以*/
byte d = 1 + 2;

System.out.println(c);
System.out.println(d);
}
}

为什么byte,short,char类型在运算的时候会自动转换成int?

本质上说,在定义byte,short类型时,他们接受的是一个int类型的数据,然后自己把它们截成了自己的类型。(如果数据超出了他们的范围,那么就会报错)

1
2
int b = a++  /*先将a赋给b , 然后a自增*/
int b = ++a /*先a自增 , 然后将a赋给b*/

各个类型的转换

1567904761965

强制转化

1
2
3
4
5
6
7
// 数据溢出
public class hello4{
public static void main(String[] args){
byte c = (byte) 130;
System.out.println(c); /*-126*/
}
}

注意 : 整形的默认类型是int

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class hello4{
public static void main(String[] args){
int money = 1000000000;
int years = 20;

/*因为超过了int的范围 , 返回负数*/
int total1 = money * years; /*-1474836480*/
/*默认是int , 因此结果会转成int,再转为long . 但是已经发生了数据丢失*/
long total2 = money * years; /*-1474836480*/
/*先将一个因子变成long , 整个表达式发生了提升 , 全部用long来计算*/
long total3 = money * (long) years; /*20000000000*/
long total4 = (long) money * years; /*20000000000*/

System.out.println(total1);
System.out.println(total2);
System.out.println(total3);
System.out.println(total4);
}
}

原地算法的强制转化类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class hello4{
public static void main(String[] args){
byte a = 1;
a = a + 1;
/*错误: 不兼容的类型: 从int转换到byte可能会有损失*/
System.out.println(a);
}
}

public class hello4{
public static void main(String[] args){
byte a = 1;
a += 1;
System.out.println(a); /*2*/
}
}

原因:

  • 扩展的赋值运算符其实隐含了一个强制类型转换
  • 所以说,a +=1并不是等价于a = a + 1 而是等价于a =(a的数据类型) (a + 1)

Scanner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Scanner;

public class hello4{
public static void main(String[] args){
Scanner scaner = new Scanner(System.in);
System.out.println("your name?");
String name = scaner.nextLine();

System.out.println("your age?");
int age = scaner.nextInt();

System.out.println(name);
System.out.println(age);
}
}

控制语句

if

1
2
3
4
5
6
7
8
9
10
11
12
13
public class hello1{
public static void main(String[] args){
char a = 'a';
char b = 'b';
if (a == 'a'){
System.out.println("111");
}else if(a == 'y'){
System.out.println("222");
}else{
System.out.println("333");
}
}
}

Switch

  • 表达式限定byte,short,int,char,string。
  • Case后面只能接常量,不能是变量
  • break中断switch语句。不加break则会穿透执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class hello2{
public static void main(String[] args){
byte a = 3;
switch (a){
case 3:
System.out.println("111");
break;
case 4:
System.out.println("222");
break;
default:
System.out.println("333");
}
}
}

for

1
2
3
4
5
6
7
public class hello3{
public static void main(String[] args){
for(int a=1; a<5; a++){
System.out.println(a);
}
}
}

while

1
2
3
4
5
6
7
8
9
public class hello4{
public static void main(String[] args){
byte a = 0;
while (a <5){
System.out.println(a);
a++;
}
}
}

Do

1
2
3
4
5
6
7
8
9
public class hello3{
public static void main(String[] args){
int a = 1;
do{
System.out.println(a);
a++;
}while(a != 3);
}
}

带标签的break和continue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class hello {
public static void main(String[] args) {
outer:
for (int a = 10; a > 5; a--) {
System.out.println("a : " + a);
inner:
for (int b = 0; b < a; b++) {
System.out.println("b : " + b);

if (b == 3) {
break outer;
}
}
}
}
}

/*a : 10
b : 0
b : 1
b : 2
b : 3*/

面向对象和面向过程

Java里的has a

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
public class student{
String name;
int age;
int id;
/*电脑*/
Computer computer;

void play_game(){
System.out.println("i play_game with "+computer.brand);
}

public static void main(String[] args) {
student student = new student();
student.name = "hyl";
student.age = 21;
student.id = 311;

/*has a*/
Computer com = new Computer();
com.brand = "lenovo";
com.money = 3500;

student.computer = com;

student.play_game();
}
}


class Computer{
String brand;
int money;

void show_brand(String brand){
System.out.println(brand);
}
}

方法

1
2
3
4
修饰符   返回值类型   方法名(参数类型   参数名1.参数类型   参数名2...){
语句;
return 返回值;
}

eg:

1
2
3
4
5
6
7
8
9
10
11
12
public class hello4 {
public static void main(String[] args) {
int a = 10;
int res = my_sum(a);
System.out.println(res); /*11*/
}

public static int my_sum(int a) {
int c = a + 1;
return c;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class hello5 {
public static void main(String[] args) {
/*通过类实例调用方法*/
hello5 hh = new hello5();
System.out.println(hh.test());
/*直接调用方法*/
System.out.println(test());
}

public static int test() {
return 5;
}
}

递归调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class hello2 {
public static void main(String[] args) {
System.out.println(febo(5));
}

static int febo(int num) {
if (num <= 2) {
return num;
} else {
return febo(num - 1) + febo(num - 2);
}
}
}

方法重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class hello5 {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = 4;
System.out.println(sum(a, b));
System.out.println(sum(b, c));
System.out.println(sum(a, b, c));
}

public static int sum(int a, int b) {
return a + b;
}

public static float sum(float a, float b) {
return a + b;
}

public static int sum(int a, int b, int c) {
return a + b + c;
}
}

类的构造方法和构造方法的重载

1
Student hyl = new Student();
  1. 构造方法通过new关键字调用
  2. 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值。
  3. 如果我们没有定义构造器,则编译器会自动定义一个无参的构造函数。如果已定义则编译器不会自动添加!
  4. 构造器的方法名必须和类名一致!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class student{
String name;
int age;
int id;

public student(String name,int age,int id){
this.name = name;
this.age = age;
this.id = id;
}

public student(String name,int age){
this.name = name;
this.age = age;
}

public static void main(String[] args) {
student studnet1 = new student("hyl",21,311);
student studnet2 = new student("dsz",55);

System.out.println(studnet1.name);
System.out.println(studnet2.age);
}
}

Java的构造方法里调用其他构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class student3{
String name;
int age;
int id;

public student3(String name,int age){
this.name = name;
this.age = age;
}

public student3(String name,int age,int id){
/*在构造器里调用其他构造器 , 使用this*/
this(name,age);
this.id = id;
}
}

创建一个对象分为如下四步:

  1. 分配对象空间,并将对象成员变量初始化为0或空
  2. 执行属性值的显式初始化
  3. 执行构造方法
  4. 返回对象的地址给相关的变量

静态修饰符static

用static声明的成员变量为静态成员变量,也称为类变量

简单来说 : static修饰的成员变量和方法从属于类 , 而不是从属于对象。普通变量和方法从属于对象的。

被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

类变量的生命周期和类相同,在整个应用程序执行期间都有效。

static是静态修饰符,什么叫静态修饰符呢?

在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,也就是只要程序在运行,那么这块内存就会一直存在。

这样做有什么意义呢?在Java程序里面,所有的东西都是对象,而对象的抽象就是类,对于一个类而言,如果要使用他的成员,那么普通情况下必须先实例化对象后,通过对象的引用才能够访问这些成员,但是用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
29
30
31
public class Student{
/*非静态变量*/
String name;
int age;
/*静态变量*/
static String school = "gdgydx";

public Student(String name,int age){
this.name = name;
this.age = age;
}
/*非静态方法*/
public void print_name(){
System.out.println(name);
}
/*静态方法*/
public static void print_school(){
// print_name(); error:不能在静态方法里调用非静态变量
System.out.println(school);
}

public static void main(String[] args) {
Student hyl = new Student("hyl",21);
/*静态方法获取静态变量*/
hyl.print_school();

hyl.school = "new school";
hyl.print_school();

}
}

总结:

  1. 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,静态变量存储在静态区
  2. 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。 实例变量存储在堆

static语句块

在类加载的时候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
29
30
public class Student {
static String school = "gdgydx";

static {
int a = 4;
System.out.println("AAA");
System.out.println("BBB");
System.out.println(a);
print_school();
}

String name;
int age;

static void print_school() {
System.out.println(school);
}

public static void main(String[] args) {
System.out.println("--- start ---");
}
}

/*
AAA
BBB
4
gdgydx
--- start ---
*/

如果类有继承关系,那么static语句块的执行顺序就是从父类往下走。构造方法的执行顺序也是从父类往下走。这种方案称为继承树的追溯

参数传值机制

Java的参数传值机制都是值传递

  • 基本数据类型参数的传值传递的是值的副本。副本改变不会影响原件。

  • 引用类型参数的传值传递的是值的副本。但是引用类型指的是对象的地址。因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Student {
static String school = "gdgydx";
String name;
int age;

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

void change_age(Student s) {
s.age = 55;
}

public static void main(String[] args) {
Student hyl = new Student("hyl", 21);
System.out.println(hyl.age); /*21*/
hyl.change_age(hyl);
System.out.println(hyl.age); /*55*/
}
}

Java虚拟机的内存管理

分成三个区域:

栈 Stack:存放局部变量

  • 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
  • JVM 每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
  • 栈属于线程私有,不能实现线程间的共享!
  • 栈是由系统自动分配,速度快。栈是一个连续的内存空间。

堆 heap存放所有类的实例

  • 堆用于存储创建好的对象和数组(数组也是对象)
  • JVM只有一个堆,被所有线程共享
  • 堆是一个不连续的内存空间,分配灵活,速度慢!

方法区 method area:也叫静态区,类似于 C 中的静态区+代码区,存放永远不变或唯一的内容。

  • 方法区用来存放程序中永远不变或唯一的内容。(类信息[Class对象]、静态变量、字符串常量等)
  • JVM只有一个方法区,被所有线程共享。
  • 方法区实际也是堆,只是用于存储类、常量相关的信息。

示例:

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
public class student {
String name;
int age;
int id;

Computer computer;

void play_game() {
System.out.println("i play_game with " + computer.brand);
}

public static void main(String[] args) {
student student = new student();
student.name = "hyl";
student.age = 21;
student.id = 311;

/*has a*/
Computer com = new Computer();
com.brand = "lenovo";
com.money = 3500;

student.computer = com;

student.play_game();
}
}


class Computer {
String brand;
int money;

void show_brand(String brand) {
System.out.println(brand);
}
}

Java内存管理

简单来说,一旦要执行某个方法

  1. 创建一个栈帧,如果这个方法再调用其他方法,则会再创建一个栈帧
  2. 一旦栈帧里的变量需要用到,就会去堆和静态区里找

继承

  • Java只有单继承
  • 子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法)但不见得可以直接访问(比如,父类私有的属性和方法)。
  • 定义一个类时,没有调用extendse的父类是:java.lang.Object
  • 判断hyl是不是Person类的实例对象:System.out.println(hyl instanceof Person);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
int age;
String name;

public void rest(Student s) {
System.out.println(s.name + " is resting now"); /*hyl is resting now*/
}
}

/*Student继承Person*/
class Student extends Person {
int stu_id;
}

class TestExtends {
public static void main(String[] args) {
Student hyl = new Student();
hyl.name = "hyl";
hyl.age = 21;
hyl.stu_id = 5;
hyl.rest(hyl);
}
}

方法重写

方法的重写需要符合下面的三个要点:

  1. ==:方法名,形参列表相同
  2. <=返回值类型声明异常类型,子类小于等于父类
  3. >=:访问权限,子类大于等于父类
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
class Person {
int age;
String name;

public void rest() {
System.out.println("Person resting now");
}

public Person who_is() {
return new Person();
}
}


/*Student继承Person*/
class Student extends Person {
int stu_id;

public void rest() {
System.out.println("Student resting now");
}

/*Student类型小于Person类型*/
public Student who_is() {
return new Student();
}
}


class TestExtends {
public static void main(String[] args) {
Student hyl = new Student();
hyl.rest(); /*Person resting now*/
System.out.println(hyl.who_is()); /*Student@15db9742*/
}
}

Object类是所有Java类的根基类,也就意味着所有的Java对象都拥有Object类的属性和方法。

Object类的方法

toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*重写toString()*/
public class Student{
String name;
int age;
static String school = "gdgydx";

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

public String toString(){
return name + age;
}

public static void main(String[] args) {
Student hyl = new Student("hyl",21);
System.out.println(hyl.toString()); /*hyl21*/
}
}

equals()

== 比较的是地址 。equals源码 :

1
2
3
public boolean equals(Object obj){
return (this == obj)
}

也就是说,Object里的equals也是比较地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Student{
String name;
int age;

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

public static void main(String[] args) {
Student s1 = new Student("hyl",21);
Student s2 = new Student("hyl",21);

System.out.println(s1==s2); /*false*/
System.out.println(s1.equals(s2)); /*false*/
}
}

但是我们可以重写equals , 使其不是比较地址

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
/*重写equals()*/
public class Student{
int id;
String name;

public Student(int id , String name){
this.id = id;
this.name = name;
}

public boolean equals(Object obj){
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
/*强制转换类型*/
Student s = (Student) obj;
if (id != s.id)
return false;
return true;
}

public static void main(String[] args) {
Student s1 = new Student(1,"hyl");
Student s2 = new Student(1,"hyl");

System.out.println(s1==s2); /*false*/
System.out.println(s1.equals(s2)); /*true*/
}
}

super()

super是直接父类对象的引用。可以通过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
26
27
28
29
30
31
class TestExtends {
public static void main(String[] args) {
Student hyl = new Student();
new Student().func();
}
}


class Person {
public int age;

public void func() {
age = 100;
System.out.println("Person.age is " + age);
}
}


class Student extends Person {
public int age;

public void func() {
/*调用父类对象的普通方法*/
super.func();
age = 200;
System.out.println("Student.age is " + age);
System.out.println(age);
/*调用父类对象的属性*/
System.out.println(super.age);
}
}

构造器方法的调用顺序

构造方法第一句总是:super(),调用父类对应的构造方法。但是一般省略不写。

所以流程就是:先向上追溯Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。

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
class Person {
public Person() {
// super(); 实际上,这里有super(); 但是省略了
System.out.println("here is Person __new__");
}
}


class Student extends Person {
public Student() {
// super(); 实际上,这里会有super(); 但是省略了
System.out.println("here is Student __new__");
}
}


class TestExtends {
public static void main(String[] args) {
System.out.println("--- start ---");
new Student();
}
}
/*
--- start ---
here is Person __new__
here is Student __new__
*/

封装

访问控制符

Java 中的访问控制权限可以分为 4 级

修饰符 类内部 同一个包 子类 任何地方
private Yes
default Yes Yes
protected Yes Yes Yes
public Yes Yes Yes Yes
1
2
3
4
5
6
7
8
9
10
11
class hellojava {
public static void main(String[] args) {
Student hyl = new Student();
System.out.println(hyl.age); // error
}
}


class Student {
private int age;
}

类属性的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TestExtends{
public static void main(String[] args) {
Student hyl = new Student();
System.out.println(hyl.getAge());
hyl.setAge(21);
System.out.println(hyl.getAge());

}
}

class Person{
private int age;

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

public int getAge(){
return this.age;
}
}

这种方式被称为JavaBean:

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
47
48
class Person {
private String name;
private int age;
public Person() {

}
public Person(String name, int age) {
this.name = name;
// this.age = age;//构造方法中不能直接赋值,应该调用setAge方法
setAge(age);
}

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//在赋值之前先判断年龄是否合法
if (age > 130 || age < 0) {
this.age = 18;//不合法赋默认值18
} else {
this.age = age;//合法才能赋值给属性age
}
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

public class Test2 {
public static void main(String[] args) {
Person p1 = new Person();
//p1.name = "小红"; //编译错误
//p1.age = -45; //编译错误
p1.setName("小红");
p1.setAge(-45);
System.out.println(p1);

Person p2 = new Person("小白", 300);
System.out.println(p2);
}
}

多态

多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同

多态的要点:

  1. 多态是方法的多态,不是属性的多态(多态与属性无关)
  2. 多态的存在要有3个必要条件:
    • 继承,
    • 方法重写,
    • 父类引用指向子类对象
  3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
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
class Animal {
public void shout() {
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("旺旺旺!");
}
public void seeDoor() {
System.out.println("看门中....");
}
}
class Cat extends Animal {
public void shout() {
System.out.println("喵喵喵喵!");
}
}
public class TestPolym {
public static void main(String[] args) {
Animal a1 = new Cat(); // 向上可以自动转型
//传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
animalCry(a1);
Animal a2 = new Dog();
animalCry(a2);//a2为编译类型,Dog对象才是运行时类型。

//编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
// 否则通不过编译器的检查。
Dog dog = (Dog)a2;//向下需要强制类型转换
dog.seeDoor();
}

// 有了多态,只需要让增加的这个类继承Animal类就可以了。
static void animalCry(Animal a) {
a.shout();
}

/* 如果没有多态,我们这里需要写很多重载的方法。
* 每增加一种动物,就需要重载一种动物的喊叫方法。非常麻烦。
static void animalCry(Dog d) {
d.shout();
}
static void animalCry(Cat c) {
c.shout();
}*/
}

结果为:

1
2
3
喵喵喵喵!
旺旺旺!
看门中....

转型

父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。

向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型

1
2
3
Animal a2 = new Dog();
// a2在编译时是 Animal 类
// a2在运行时是 Dog 类
1
2
3
4
5
6
7
8
9
10
11
public class TestCasting {
public static void main(String[] args) {
Object obj = new String("hyl"); // 向上可以自动转型
// obj.charAt(0) 无法调用。编译器认为obj是Object类型而不是String类型
/* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
* 不然通不过编译器的检查。 */
String str = (String) obj; // 向下转型
System.out.println(str.charAt(0)); // 位于0索引位置的字符
System.out.println(obj == str); // true.他们俩运行时是同一个对象
}
}

编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。不然通不过编译器的检查

向上可以自动转型:

1
Animal a1 = new Cat();

向下需要强制类型转换:

1
2
Animal a2 = new Dog();
Dog dog = (Dog)a2;

final关键字

  • 修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
  • 修饰方法:该方法不可被子类重写。但是可以被重载
  • 修饰类: 修饰的类不能被继承。比如:Math、String等。
1
2
3
final  int   MAX_SPEED = 120;
final void study(){}
final class A {}

抽象方法和抽象类

  • 抽象方法:使用 abstract 修饰的方法,没有方法体,只有声明。定义的是一种规范,就是告诉子类必须要给抽象方法提供具体的实现。
  • 抽象类:包含抽象方法的类就是抽象类。通过 abstract 方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Student{
abstract public void study();
public void run(){
System.out.println("i am runing");
}

}

class HYL extends Student{
public void study(){
System.out.println("i am studying");
}
}


public class TestExtends{
public static void main(String[] args) {
HYL hyl = new HYL();
hyl.study();
hyl.run();
}
}

抽象类的使用要点:

  1. 有抽象方法的类只能定义成抽象类。
  2. 抽象类不能实例化,即不能用new来实例化抽象类
  3. 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
  4. 抽象类只能用来被继承。
  5. 抽象方法必须被子类实现。

接口

为什么需要接口?接口和抽象类的区别?

  • 接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束
  • 全面地专业地实现了:规范和具体实现的分离
  • 抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类员有的公共方法规范。

接口就是规范,定义的是一组规则,体现了现实世界中如果你是……,则必须能……的思想。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。如果你是好人,则必须能干掉坏人。如果你是坏人,则必须欺负好人。

  • 从接口的实现者角度看,接口定义了可以向外部提供的服务。
  • 从接口的调用者角度看,接口定义了实现者能提供那些服务。

定义接口的详细说明

  1. 访问修饰符:只能是public或默认。
  2. 接口名:和类名采用相同命名机制。
  3. extends:接口可以多继承。
  4. 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
  5. 方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract。

要点:

  1. 子类通过implements来实现接口中的规范。
  2. 接口不能创建实例,但是可用于声明引用变量类型。
  3. 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
  4. 接口中只能包含静态常量、抽象方法,静态方法,不能有普通属性、构造方法、普通方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface MyInterface1{
public static final int MAX_AGE = 100;
void func1();
}

public interface MyInterface2{
void func2();
}


/*类可以继承都个接口*/
class MyClass implements MyInterface1,MyInterface1{
public void func(){
System.out.println(MAX_AGE);
System.out.println("...");
}
}

内部类

把一个类放在另一个类的内部定义,称为内部类(innerclasses)。内部类可以使用public、default、protected 、private以及static修饰。而外部顶级类只能使用publicdefault修饰。

  • 内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。
  • 对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后会出现Outer.class和Outer$Inner.class两个类的字节码文件。
  • 所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。

内部类的作用:

  1. 内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问
  2. 内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。 但外部类不能访问内部类的内部属性。
  3. 接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。

内部类的分类

内部类主要分为

  • 成员内部类(非静态内部类、静态内部类)、
  • 匿名内部类、
  • 局部内部类。

非静态内部类静态内部类的区别就是classstatic class

成员内部类

非静态内部类

外部类里使用非静态内部类和平时使用其他类没什么不同

  1. 非静态内部类必须寄存在一个外部类对象里。因此,如果有一个非静态内部类对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
  2. 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员。
  3. 非静态内部类不能有静态方法、静态属性和静态初始化块。
  4. 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestExtends {
public static void main(String[] args) {
/*创建内部类对象*/
Outer.Inner inner = new Outer().new Inner();
inner.show();
}
}


class Outer {
private int age = 10;

class Inner {
int age = 20;

public void show() {
int age = 30;
System.out.println(age); /*30*/
System.out.println(this.age); /*20*/
System.out.println(Outer.this.age); /*10*/
}
}
}

非静态内部类有点类似于python中的闭包:

1
2
3
4
5
6
7
8
def func():
a = 1
def inner():
print(a)
return inner()

func() # 1
inner() # 报错
  • 内部函数能获取外部函数的变量
  • 内部函数只能由外部函数来调用
静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestExtends {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}


class Outer {
private int age = 10;

static class Inner {
int age = 20;

public void show() {
int age = 30;
System.out.println(age); /*30*/
System.out.println(this.age); /*20*/
}
}
}

使用要点:

  1. 当一个静态内部类对象存在,并不一定存在对应的外部类对象。 因此,静态内部类的实例方法不能直接访问外部类的实例方法。
  2. 静态内部类看做外部类的一个静态成员。 因此,外部类的方法中可以通过:静态内部类.名字的方式访问静态内部类的静态成员,通过 new 静态内部类()访问静态内部类的实例。

匿名内部类

适合那种只需要使用一次的类。比如:键盘监听操作等等。

  1. 匿名内部类没有访问修饰符。
  2. 匿名内部类没有构造方法。因为它连名字都没有那又何来构造方法呢。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestExtends {
public static void main(String[] args) {
TestExtends.function(new Outer() {
public void func() {
System.out.println("new.Outer.func()");
}
});
}

public static void function(Outer out) {
out.func();
}
}


class Outer {
public void func() {
System.out.println("Outer.func()");
}
}
/*new.Outer.func()*/

局部内部类

类定义在方法内部的,作用域只限于本方法,称为局部内部类

局部内部类的的使用主要是用来解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法中被使用,出了该方法就会失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test2 {
public static void main(String[] args) {
new Test2().show();
}

public void show() {
//作用域仅限于该方法
class Inner {
public void fun() {
System.out.println("helloworld");
}
}
new Inner().fun();
}
}

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class student3 {
public static void main(String[] args) {
int[] arr = new int[5];

/*通过循环初始化*/
for (int i = 0; i < arr.length; i++) {
arr[i] = i * 10;
}

/*通过循环取值*/
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
/*
0
10
20
30
40
*/

1495418560857133

数组的初始化

数组的初始化方式总共有三种:

  • 静态初始化
  • 动态初始化
  • 默认初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class student3{
public static void main(String[] args) {
/*静态初始化*/
int[] arr1 = {1,2,3,4,5};

/*默认初始化,数组元素有默认值*/
int[] arr2 = new int[3];

/*动态初始化,先分配空间,再给元素赋值*/
int[] arr3 = new int[2];
arr3[0] = 1;
arr3[1] = 2;
}
}

foreach循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class student3{

public static void main(String[] args) {
int[] arr1 = {1,2,3,4,5};

for(int i=0; i <arr1.length; i++){
System.out.println(arr1[i]);
}
/*for each 循环 : 只读,不能修改数组元素*/
for(int each : arr1){
System.out.println(each);
}
}
}

数组的拷贝,删除,插入,扩容

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
// 拷贝
public class Student {
public static void main(String[] args) {
String[] s1 = { "aa", "bb", "cc", "dd", "ee" };
String[] s2 = new String[10];
/*从s1拷贝到s2,从s1索引为2开始,一共拷贝s1的3个元素,放到s2索引为6的位置*/
System.arraycopy(s1, 2, s2, 6, 3);

for (int i = 0; i < s2.length; i++) {
System.out.println(s2[i]);
}
}
}
/*
null
null
null
null
null
null
cc
dd
ee
null
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 删除 : 本质还是数组的拷贝
/*删除索引为3的元素*/
public class Student{
public static void main(String[] args) {
String[] s1 = {"aa","bb","cc","dd","ee"};

System.arraycopy(s1,3,s1,2,s1.length-3);
s1[s1.length-1] = null;

for(int i=0; i<s1.length ;i++){
System.out.println(s1[i]);
}
}
}

/*
aa
bb
dd
ee
null
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 数组的扩容
// 本质上是:先定义一个更大的数组,然后将原数组内容原封不动贝到新数组中
public class Student{
public static void main(String[] args) {
String[] s1 = {"aa","bb","cc","dd","ee"};
String[] s2 = new String[s1.length+2];

System.arraycopy(s1,0,s2,0,s1.length);

for(int i=0; i<s2.length ;i++){
System.out.println(s2[i]);
}
}
}

/*
aa
bb
cc
dd
ee
null
null
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 插入
public class Student{
public static void main(String[] args) {
String[] s1 = {"aa","bb","cc","dd","ee"};
String[] s2 = new String[s1.length+1];

System.arraycopy(s1,0,s2,0,2);
s2[2] = "XX";
System.arraycopy(s1,2,s2,3,3);

for(int i=0; i<s2.length ;i++){
System.out.println(s2[i]);
}
}
}

/*
aa
bb
XX
cc
dd
ee
*/

多维数组

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Arrays;

public class Student{
public static void main(String[] args) {
int[][] a = new int[3][];

a[0] = new int[]{1,2,3};
a[1] = new int[]{4,5,6};
a[2] = new int[]{7,8,9};
System.out.println(Arrays.toString(a)); /*[[I@15db9742, [I@6d06d69c, [I@7852e922]*/
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Arrays;

public class Student {
public static void main(String[] args) {
Object[] a1 = {1001,"高淇",18,"讲师","2006-2-14"};
Object[] a2 = {1002,"高小七",19,"助教","2007-10-10"};
Object[] a3 = {1003,"高小琴",20,"班主任","2008-5-5"};

Object[][] emps = new Object[3][];
emps[0] = a1;
emps[1] = a2;
emps[2] = a3;

System.out.println(Arrays.toString(emps[0]));
System.out.println(Arrays.toString(emps[1]));
System.out.println(Arrays.toString(emps[2]));
}
}

String类

  1. Java字符串就是Unicode字符序列,例如字符串“Java”就是4个Unicode字符’J’、’a’、’v’、’a’组成的。
  2. Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是String类的一个实例。

String str1 = "hyl";String str3 = new String("hyl");是不一样的.

1
2
3
4
5
6
7
8
9
10
11
12
public class student4 {
public static void main(String[] args) {
String str1 = "hyl";
String str2 = "hyl";
String str3 = new String("hyl");

/*str1和str2指向的内存是一样的*/
System.out.println(str1 == str2); /*true*/
/*str1和str2指向的内存是不一样的*/
System.out.println(str1 == str3); /*false*/
}
}
  • str1和str2的值hyl放在了静态区

  • str3是生成一个String对象,该对象的值将其放在静态区

    如果要判断两个字符串内容是否相等,建议使用equals:

    1
    System.out.println(str1.equals(str3));

String类的常用方法

s.png

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
public class StringTest1 {
public static void main(String[] args) {
String s1 = "core Java";
String s2 = "Core Java";
System.out.println(s1.charAt(3));//提取下标为3的字符
System.out.println(s2.length());//字符串的长度
System.out.println(s1.equals(s2));//比较两个字符串是否相等
System.out.println(s1.equalsIgnoreCase(s2));//比较两个字符串(忽略大小写)
System.out.println(s1.indexOf("Java"));//字符串s1中是否包含Java
System.out.println(s1.indexOf("apple"));//字符串s1中是否包含apple
String s = s1.replace(' ', '&');//将s1中的空格替换成&
System.out.println("result is :" + s);
}
}

public class StringTest2 {
public static void main(String[] args) {
String s = "";
String s1 = "How are you?";
System.out.println(s1.startsWith("How"));//是否以How开头
System.out.println(s1.endsWith("you"));//是否以you结尾
s = s1.substring(4);//提取子字符串:从下标为4的开始到字符串结尾为止
System.out.println(s);
s = s1.substring(4, 7);//提取子字符串:下标[4, 7) 不包括7
System.out.println(s);
s = s1.toLowerCase();//转小写
System.out.println(s);
s = s1.toUpperCase();//转大写
System.out.println(s);
String s2 = " How old are you!! ";
s = s2.trim();//去除字符串首尾的空格。注意:中间的空格不能去除
System.out.println(s);
System.out.println(s2);//因为String是不可变字符串,所以s2不变
}
}

String类源码分析

String类的部分源码 :

1
2
3
4
5
6
7
8
public final class String 
implements java.io.Serializable, Comparable<string>, CharSequence
{
// 字符串内容全部存储到value[]数组中,而变量value是final类型的,也就是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式。
private final char value[];
private final int offset;
private final int count;
}

在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用==进行String对象之间的比较时,我们需要特别注意,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestString2 {
public static void main(String[] args) {
// 编译器做了优化,直接在编译的时候将字符串进行拼接
String str1 = "hello" + " java"; //相当于str1 = "hello java";
String str2 = "hello java";
System.out.println(str1 == str2); //true

String str3 = "hello";
String str4 = " java";
// 编译的时候不知道变量中存储的是什么,所以没办法在编译的时候优化
String str5 = str3 + str4;
System.out.println(str2 == str5); //false
}
}

StringBuffer和StringBuilder

StringBuffer和StringBuilder非常类似,均代表可变的字符序列。 这两个类都是抽象类AbstractStringBuilder的子类,方法几乎一模一样。

  • StringBuffer:线程安全,做线程同步检查, 效率较低。
  • StringBuilder:线程不安全,不做线程同步检查,因此效率较高。 建议采用该类。

AbstractStringBuilder 部分源码

1
2
3
4
5
abstract class AbstractStringBuilder  implements Appendable, CharSequence {
// 内部也是一个字符数组,但这个字符数组没有用final修饰,随时可以修改。因此,StringBuilder和StringBuffer称之为“可变字符序列”。
char value[];
...
}
1
2
3
4
5
6
7
8
9
10
public class student4 {
public static void main(String[] args) {
String a;
StringBuilder sb = new StringBuilder("abcdef");
System.out.println(sb); /*abcdef*/

sb.setCharAt(2,'9');
System.out.println(sb); /*ab9def*/
}
}

StringBuilder常用方法列表

  1. 重载的public StringBuilder append(…)方法

​ 可以为该StringBuilder 对象添加字符序列,仍然返回自身对象。

  1. 方法 public StringBuilder delete(int start,int end)

​ 可以删除从start开始到end-1为止的一段字符序列,仍然返回自身对象。

  1. 方法 public StringBuilder deleteCharAt(int index)

​ 移除此序列指定位置上的 char,仍然返回自身对象。

  1. 重载的public StringBuilder insert(…)方法

​ 可以为该StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。

  1. 方法 public StringBuilder reverse()

​ 用于将字符序列逆序,仍然返回自身对象。

  1. 方法 public String toString() 返回此序列中数据的字符串表示形式。

  2. 和 String 类含义类似的方法:

    1
    2
    3
    4
    5
    6
    public int indexOf(String str)
    public int indexOf(String str,int fromIndex)
    public String substring(int start)
    public String substring(int start,int end)
    public int length()
    char charAt(int index)

可变对象与不可变对象

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
import java.util.Arrays;

public class Test {
public static void main(String[] args) {
System.out.println(func1());
System.out.println(func2());
}

public static String func1(){
/**使用String进行字符串的拼接*/
String str = "";

for (int i = 0; i < 5000; i++) {
str = str + i;
}
return str;
}

public static String func2(){
/**使用StringBuilder进行字符串的拼接*/
StringBuilder str = new StringBuilder();

for (int i = 0 ; i < 5000 ; i++){
str.append(i);
}
return str.toString();
}
}
  • str = str + i;每次都会生成新对象,超级慢。
  • str.append(i);并不会生成新对象。只是修改了原对象而已.
  • 多次修改的情况下要使用str.append(i);

Array类

  • java.util.Arrays类,包含了常用的数组操作,方便我们日常开发。
  • Arrays类包含了:排序、查找、填充、打印内容等常见的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Arrays;

public class student3{
public static void main(String[] args) {
int[] a = { 1,2,323,23,543,12,59 };

System.out.println(a); /*[I@15db9742*/
System.out.println(Arrays.toString(a)); /*[1, 2, 323, 23, 543, 12, 59]*/

Arrays.sort(a);
System.out.println(Arrays.toString(a)); /*[1, 2, 12, 23, 59, 323, 543]*/

System.out.println("index : "+ Arrays.binarySearch(a, 12)); /*index : 2*/
}
}

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Arrays;

public class Student {
public static void main(String[] args) {
int[] arr = { 7, 4, 1, 2, 5, 8, 9, 6, 3 };
bubble_sort(arr);
System.out.println(Arrays.toString(arr));
}

public static void bubble_sort(int[] arr) {
int temp;

for (int i = arr.length - 1; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
if (arr[i] < arr[j]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
}

二分查找

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
public class Student {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
System.out.println(bin_search(arr, 5));
}

public static int bin_search(int[] arr, int target) {
int left = 0;
int right = arr.length;
int mid;

while (left < right) {
mid = left + ((right - left) / 2);

if (arr[mid] < target) {
left = mid + 1;
} else {
if (arr[mid] > target) {
right = mid;
} else {
return mid;
}
}
}

return -1;
}
}