Java:梦开始的地方

What language is thine, o sea?

The language of eternal question.

What language is thy answer, o sky?

The language of eternal silence.

记性越来越差!能怎么办!

做个博客记录记录吧~

虽然整个java相关都快学完了,但总是觉得看了后面忘了前面,所以决定写个博客,正好感觉之前基础学的感觉不扎实,那就把容易忘记的东西复习一遍吧,准备明年的实习,在实习之前把所有的java相关再来一次~

永不放弃!

编译型语言和解释型语言的区别

解释性语言和编译型语言的优缺点

原文链接 版权声明

编译型语言

  • 编译型语言最大的优势之一就是其执行速度。用C/C++编写的程序运行速度要比用Java编写的相同程序快30%-70%;
  • 编译型程序比解释型程序消耗的内存更少;
  • 不利的一面——编译器比解释器要难写得多;
  • 编译器在调试程序时提供不了多少帮助——有多少次在你的C语言代码中遇到一个“空指针异常”时,需要花费好几个小时来明确错误到底在代码中的什么位置;
  • 可执行的编译型代码要比相同的解释型代码大许多。例如C/C++的.exe文件要比同样功能的Java的.class文件大很多;
  • 编译型程序是面向特定平台的因而是平台依赖的;
  • 编译型程序不支持代码中实现安全性——例如,一个编译型的程序可以访问内存的任何区域,并且可以对你的PC做它想做的任何事情(大部分病毒是使用编译型语言编写的);
  • 由于松散的安全性和平台依赖性,编译型语言不太适合开发因特网或者基于Web的应用。

解释型语言

  • 解释型语言提供了极佳的调试支持。一名Java程序员只需要几分钟就可以定位并修复一个“空指针异常”,因为Java运行环境不仅指明了异常的性质,而且给出了异常发生位置具体的行号和函数调用顺序(著名的堆栈跟踪信息)。这样的便利是编译型语言所无法提供的;
  • 另一个优势是解释器比编译器容易实现;
  • 解释型语言最大的优势之一是其平台独立性
  • 解释型语言也可以保证高度的安全性——这是互联网应用迫切需要的;
  • 中间语言代码的大小比编译型可执行代码小很多;
  • 平台独立性,以及严密的安全性是使解释型语言成为适合互联网和Web应用的理想语言的2个最重要的因素;
  • 解释型语言存在一些严重的缺点。解释型应用占用更多的内存和CPU资源。这是由于,为了运行解释型语言编写的程序,相关的解释器必须首先运行。解释器是复杂的,智能的,大量消耗资源的程序并且它们会占用很多CPU周期和内存;
  • 由于解释型应用的decode-fetch-execute(解码-抓取-执行)的周期,它们比编译型程序慢很多;
  • 解释器也会做很多代码优化,运行时安全性检查;这些额外的步骤占用了更多的资源并进一步降低了应用的运行速度。

总结

类型 原理 优点 缺点
编译型语言 通过专门的编译器,将所有源代码一次性转换成特定平台(Windows、Linux 等)执行的机器码(以可执行文件的形式存在)。 编译一次后,脱离了编译器也可以运行,并且运行效率高。 可移植性差,不够灵活。
解释型语言 由专门的解释器,根据需要将部分源代码临时转换成特定平台的机器码。 跨平台性好,通过不同的解释器,将相同的源代码解释成不同平台下的机器码。 一边执行一边转换,效率很低。

Java注释

  1. 单行注释

    以双斜杠“//”标识,只能注释一行内容,用在注释信息内容少的地方;

    1
    //这是单行注释
  2. 多行注释

    包含在“/”和“/”之间,能注释很多行的内容。为了可读性比较好,一般首行和尾行不写注释信息(这样也比较美观好看);

    • 多行注释可以嵌套单行注释,但是不能嵌套多行注释和文档注释。
    1
    2
    3
    4
    /*
    这是多行注释
    这里也是
    */
  3. 文档注释

    包含在“/*”和“/”之间,也能注释多行内容,一般用在类、方法和变量上面,用来描述其作用;

    注释后,鼠标放在类和变量上面会自动显示出我们注释的内容。

    1
    2
    3
    4
    5
    /**
    *这是文档注释
    *@Description HelloWorld
    *这是有功能的注释
    */

Java命名规范

  • 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的。

  • 常量:大写字母和下划线组成。例如MAX_VALUE

  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass

  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。例如 myFirstJavaMethod

    类成员变量、局部变量同样遵守这个规则

    方法名不使用连接符,但下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test<MethodUnderTest>_<state>,例如 testPop_emptyStack

  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。

  • 主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。

  • Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符:

    • 所有的标识符都应该以字母(A-Z 或者 a-z),美元符($)、或者下划线(_)开始
    • 首字符之后可以是字母(A-Z 或者 a-z),美元符($)、下划线(_)或数字的任何字符组合
    • 关键字不能用作标识符
    • 标识符是大小写敏感的
    • 合法标识符举例:age、$salary、_value、__1_value
    • 非法标识符举例:123abc、-salary

数据类型

部分内容引用菜鸟教程

程序员就要知道CV , CV完了千万记得理解呀!!!

Java是一种强类型语言

  • 要求变量的使用要严格符合规定,所有变量都必须先定义以后才能使用

基本数据类型

byte:

  • byte 数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是 -128(-2^7)
  • 最大值是 127(2^7-1)
  • 默认值是 0
  • byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;

short:

  • short 数据类型是 16 位、有符号的以二进制补码表示的整数
  • 最小值是 -32768(-2^15)
  • 最大值是 32767(2^15 - 1)
  • Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是 0

int:

  • int 数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是 -2,147,483,648(-2^31)
  • 最大值是 2,147,483,647(2^31 - 1)
  • 一般地整型变量默认为 int 类型;
  • 默认值是 0

long:

  • long 数据类型是 64 位、有符号的以二进制补码表示的整数;
  • 最小值是 -9,223,372,036,854,775,808(-2^63)
  • 最大值是 9,223,372,036,854,775,807(2^63 -1)
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是 0L
  • “L”理论上不分大小写,但是若写成”l”容易与数字”1”混淆,不容易分辩。所以最好大写。

float:

  • float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float 在储存大型浮点数组的时候可节省内存空间;
  • 默认值是 0.0f
  • 浮点数不能用来表示精确的值,如货币;
  • 最好完全避免使用浮点数进行比较;

double:

  • double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;

  • 浮点数的默认类型为 double 类型;

  • double类型同样不能表示精确的值,如货币;

  • 默认值是 0.0d

  • 最好完全避免使用浮点数进行比较;

  • 例子:

    1
    2
    3
    4
    5
    double   d1  = 7D ;
    double d2 = 7.;
    double d3 = 8.0;
    double d4 = 8.D;
    double d5 = 12.9867;

    7 是一个 int 字面量,而 7D,7. 和 8.0 是 double 字面量。

boolean:

  • boolean数据类型表示一位的信息;
  • 只有两个取值:true 和 false;
  • 这种类型只作为一种标志来记录 true/false 情况;
  • 默认值是 false

char:

  • char 类型是一个单一的 16 位 Unicode 字符;
  • 最小值是 \u0000(十进制等效值为 0);
  • 最大值是 \uffff(即为 65535);
  • char 数据类型可以储存任何字符;

类型转换

自动类型转换:整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。

1
2
3
低  ------------------------------------>  高

byte,short,char—> int —> long—> float —> double

数据类型转换必须满足如下规则:

  • 不能对boolean类型进行类型转换
  • 不能把对象类型转换成不相关类的对象
  • 在把容量大的类型转换为容量小的类型时必须使用强制类型转换
  • 转换过程中可能导致溢出或损失精度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ZiDongLeiZhuan{
public static void main(String[] args){
char c1='a';//定义一个char类型
int i1 = c1;//char自动类型转换为int
System.out.println("char自动类型转换为int后的值等于"+i1);
char c2 = 'A';//定义一个char类型
int i2 = c2+1;//char 类型和 int 类型计算
System.out.println("char类型和int计算后的值等于"+i2);
}
}

/*
运行结果
char自动类型转换为int后的值等于97
char类型和int计算后的值等于66
*/

强制类型转换:

  • 条件是转换的数据类型必须是兼容的
  • 格式:(type)value type是要强制类型转换后的数据类型
1
2
3
4
5
6
7
8
9
10
11
12
public class QiangZhiZhuanHuan{
public static void main(String[] args){
int i1 = 123;
byte b = (byte)i1;//强制类型转换为byte
System.out.println("int强制类型转换为byte后的值等于"+b);
}
}

/*
运行结果
int强制类型转换为byte后的值等于123
*/

隐含强制类型转换:

  • 整数的默认类型是 int
  • 小数默认是 double 类型浮点型,在定义 float 类型时必须在数字后面跟上 F 或者 f

引用数据类型

  • 引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时候被指定为一个特定的类型,声明之后不能改变。
  • 引用数据类型包括:类(对象)、接口、数组
  • 所有引用类型默认值都是null.
  • 一个引用变量可以用于引用任何与之兼容的类型。

区别

  • 基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上
  • 引用数据类型在被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址。

Java变量类型

Java语言支持的变量类型有:

  • 类变量:独立于方法之外的变量,用 static 修饰。
  • 实例变量:独立于方法之外的变量,不过没有 static 修饰。
  • 局部变量:类的方法中的变量。
1
2
3
4
5
6
7
public class Variable{
static int allClicks=0; // 类变量
String str="hello world"; // 实例变量
public void method(){
int i =0; // 局部变量
}
}

局部变量

  • 局部变量声明在方法、构造方法或者语句块中
  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁
  • 访问修饰符不能用于局部变量(default private public protected)
  • 局部变量只在声明它的方法、构造方法或者语句块中可见
  • 局部变量是在上分配的
  • 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class Test{
public void pupAge(){
int age = 0;
age = age + 7;
System.out.println("小狗的年龄是: " + age);
}
public static void main(String[] args){
Test test = new Test();
test.pupAge();
}
}

/*
运行结果
小狗的年龄是: 7
*/

实例变量

  • 实例变量声明在一个类中,但在方法、构造方法和语句块之外
  • 当一个对象被实例化之后,每个实例变量的值就跟着确定
  • 实例变量在对象创建的时候创建,在对象被销毁的时候销毁
  • 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息
  • 实例变量可以声明在使用前或者使用后
  • 访问修饰符可以修饰实例变量(default private public protected)
  • 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见
  • 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定
  • 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。
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
import java.io.*;
public class Employee{
// 这个实例变量对子类可见
public String name;
// 私有变量,仅在该类可见
private double salary;
//在构造器中对name赋值
public Employee (String empName){
name = empName;
}
//设定salary的值
public void setSalary(double empSal){
salary = empSal;
}
// 打印信息
public void printEmp(){
System.out.println("名字 : " + name );
System.out.println("薪水 : " + salary);
}

public static void main(String[] args){
Employee empOne = new Employee("BOBO");
empOne.setSalary(1000.0);
empOne.printEmp();
}
}

/*
运行结果
$ javac Employee.java
$ java Employee
名字 : BOBO
薪水 : 1000.0
*/

类变量

点击跳转静态变量

  • 类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法之外
  • 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝
  • 静态变量除了被声明为常量外很少使用,静态变量是指声明为 public/private,final 和 static 类型的变量。静态变量初始化后不可改变
  • 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
  • 静态变量在第一次被访问时创建,在程序结束时销毁
  • 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型
  • 默认值和实例变量相似。数值型变量默认值是 0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化
  • 静态变量可以通过:ClassName.VariableName的方式访问
  • 类变量被声明为 public static final 类型时,类变量名称一般建议使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.*;
public class Employee {
//salary是静态的私有变量
private static double salary;
// DEPARTMENT是一个常量
public static final String DEPARTMENT = "开发人员";
public static void main(String[] args){
salary = 10000;
System.out.println(DEPARTMENT+"平均工资:"+salary);
}
}

/*
运行结果
开发人员平均工资:10000.0
*/

Java方法

可变参数

JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。

方法的可变参数的声明如下所示:

1
typeName... parameterName

在方法声明中,在指定参数类型后加一个省略号(…) 。

一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。

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
public class VarargsDemo {
public static void main(String args[]) {
// 调用可变参数的方法
printMax(34, 3, 3, 2, 56.5);
printMax(new double[]{1, 2, 3});
}
public static void printMax( double... numbers) {
if (numbers.length == 0) {
System.out.println("No argument passed");
return;
}
double result = numbers[0];
for (int i = 1; i < numbers.length; i++){
if (numbers[i] > result) {
result = numbers[i];
}
}
System.out.println("The max value is " + result);
}
}

/*
运行结果
The max value is 56.5
The max value is 3.0
*/

构造方法

  • 构造方法也称为构造器
  • 当一个对象被创建时候,构造方法用来初始化该对象
  • 构造方法和它所在类的名字相同,但构造方法没有返回值,也不能写void
  • 通常会使用构造方法给一个类的实例变量赋初值,或者执行其它必要的步骤来创建一个完整的对象
  • 不管你是否自定义构造方法,所有的类都有构造方法,因为 Java 自动提供了一个默认构造方法,默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;类改为 protected,构造函数也改为 protected)
  • 一旦你定义了自己的构造方法,默认构造方法就会失效
1
2
3
4
5
6
7
8
9
// 一个简单的构造函数
class MyClass {
int x;

// 以下是构造函数
MyClass() {
x = 10;
}
}

Java 静态类、静态方法和静态变量

static 是Java中的一个关键字,我们不能声明普通外层类或者包为静态的

静态变量

点击跳转类变量

静态变量即类变量,静态变量是属于类的,而不是属于类创建的对象或实例

1
2
3
4
//静态变量的例子
private static int count;
public static String str;
public static final String DB_USER = "myuser"

静态方法

  • 类似于静态变量
  • 静态方法也属于类,不属于实例
  • 静态方法只能访问类的静态变量,或调用类的静态方法
  • 通常静态方法作为工具方法,被其它类使用,而不需要创建类的实例
  • 通常java程序的开始就是一个main()方法,它就是个静态方法
  • 静态方法只会在第一次执行一次
1
2
3
4
5
6
7
8
9
10
11
12
//静态方法的例子
public static void setCount(int count) {
if(count > 0)
StaticExample.count = count;
}

//静态工具方法
public static int addInts(int i, int...js){
int sum=i;
for(int x : js) sum+=x;
return sum;
}

静态块

  • 静态块就是类加载器加载对象时,要执行的一组语句
  • 用于初始化静态变量
  • 通常用于类加载的时候创建静态资源
  • 我们在静态块中不能访问非静态变量
  • 我们可以在一个类中有多个静态块,尽管这么做没什么意义
  • 静态块只会在类加载到内存中的时候执行一次
1
2
3
4
5
6
7
static{
//在类被加载的时候用于初始化资源
System.out.println("StaticExample static block");
//仅能访问静态变量和静态方法
str="Test";
setCount(2);
}

静态类

点击跳转内部类

  • 我们对嵌套类使用static关键字
  • static不能用于最外层的类
  • 静态的嵌套类和其它外层的类别无二致,嵌套只是为了方便打包

静态导入包

1
import static java.lang.Math.random;

可以在类中直接使用random方法

Java数组

普通数组

数组是相同类型数据有序集合

  • 长度确定,一旦被创建,大小就不可以被改变
  • 其元素必须是相同类型,不允许出现混合类型
  • 数组中的元素可以是任何数据类型
  • 数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量

初始化数组

1
2
3
4
5
6
// 首选的方法
dataType[] arrayRefVar;
//或
// 效果相同,但不是首选方法
// 来自 C/C++ 语言
dataType arrayRefVar[];

初始化多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//直接为每一维分配空间
//type 可以为基本数据类型和复合数据类型,typeLength1 和 typeLength2 必须为正整数,typeLength1 为行数,typeLength2 为列数
type[][] typeName = new type[typeLength1][typeLength2];
//或
//为最高维分配引用空间
//再为其每个数组元素单独分配空间
String[][] s = new String[2][];
s[0] = new String[2];
s[1] = new String[3];
s[0][0] = new String("Good");
s[0][1] = new String("Luck");
s[1][0] = new String("to");
s[1][1] = new String("you");
s[1][2] = new String("!");

稀疏数组

稀疏矩阵(英语:sparse matrix)指的是在数值分析中绝大多数数值为零的矩阵。反之,如果大部分元素都非零,则这个矩阵是稠密的(Dense)。

上图中左边就是一个稀疏矩阵,可以看到包含了很多 0 元素,右边是稠密的矩阵,大部分元素不是 0

转化为稀疏数组

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 int[][] sparseMatrix(int[][] array){
int sum = 0;
for(int i = 0;i < array.length; i++){
for(int j = 0; j < array[i].length){
if(array[i][j] != 0)
sum++;
}
}
System.out.println("有效值的个数:" + sum);

//创建一个稀疏数组
int[][] sparse = new int[sum + 1][3];
sparse[0][0] = array.length;
sparse[0][1] = array[0].length;
sparse[0][2] = sum;

//遍历二维数组,将非零的值,存放数组中
int count = 0;
for(int i = 0 ; i < array.length ; i++){
for(int j = 0 ; j < array[0].length ; j++){
if(array[i][j] != 0){
count++;
sparse[count][0] = i;
sparse[count][1] = j;
sparse[count][2] = array[i][j];
}
}
}
return sparse;
}

Java类

Java Scanner 类

1
Scanner s = new Scanner(System.in);

通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要 使用 hasNext 与 hasNextLine 判断是否还有输入的数据.

next方法

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

public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据

// next方式接收字符串
System.out.println("next方式接收:");
// 判断是否还有输入
if (scan.hasNext()) {
String str1 = scan.next();
System.out.println("输入的数据为:" + str1);
}
scan.close();
}
}

/*
运行结果
$ javac ScannerDemo.java
$ java ScannerDemo
next方式接收:
bobo com
输入的数据为:bobo
*/

nextLine 方法

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

public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据

// nextLine方式接收字符串
System.out.println("nextLine方式接收:");
// 判断是否还有输入
if (scan.hasNextLine()) {
String str2 = scan.nextLine();
System.out.println("输入的数据为:" + str2);
}
scan.close();
}
}

/*
运行结果
$ javac ScannerDemo.java
$ java ScannerDemo
nextLine方式接收:
bobo com
输入的数据为:bobo com
*/

next() 与 nextLine() 区别

next():

  • 一定要读取到有效字符后才可以结束输入。
  • 对输入有效字符之前遇到的空白,next() 方法会自动将其去。
  • 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next() 不能得到带有空格的字符串。

nextLine():

  • 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  • 可以获得空白。

相较于 hasnext(),针对int、float等其他基本数据类型,还有scan.hasNextInt()、hasNextFloat()等方法,返回布尔值;

相较于 nextLine(),针对int、float等其他基本数据类型,还有、scan.nextInt()、scan.nextFloat()等方法,返回输出类型值;

Java Arrays 类

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。

具有以下功能:

  • 给数组赋值:通过 fill 方法 ,将指定的值分配给数组的每个元素
  • 对数组排序:通过 sort 方法,按升序
  • 比较数组:通过 equals 方法比较数组中元素值是否相等
  • 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作

内部类

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。

成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

1
2
3
4
5
6
7
8
9
10
11
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}

类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。


不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

1
2
外部类.this.成员变量
外部类.this.成员方法

虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() { //创建成员内部类的对象
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}

成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

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 Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建

//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {

}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}

内部类可以拥有 private 访问权限、protected 访问权限、public 访问权限及包访问权限。比如上面的例子,如果成员内部类 Inner 用 private 修饰,则只能在外部类的内部访问,如果用 public 修饰,则任何地方都能访问;如果用 protected 修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被 public 和包访问两种权限修饰。


局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}

注意: 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。


匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。

下面是一段匿名内部类的代码:

1
2
3
4
5
6
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}

下面这段代码是一段 Android 事件监听代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});

history_bt.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});

这就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。


静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。

静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

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

}
static class Inner {
public Inner() {

}
}
}

静态内部类与内部类的区别

内部类

  • 内部类拥有普通类的所有特性,也拥有类成员变量的特性
  • 内部类可以访问其外部类的成员变量,属性,方法,其它内部类

静态内部类

  • 只有内部类才能声明为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
/**
* 外部类,不能声明为 static
*/
public class OuterClass {
private int ab = 1;
private static int sab = 2;
/**
* 普通内部类
*/
public class NormalInnerClass {
// private static int age = 22;
private int age = 22; // 不能声明为static
public NormalInnerClass() {
// 可以访问外部类静态与非静态成员
System.out.println(ab);
System.out.println(sab);
}
}
/**
* 静态内部类
*/
public static class StaticInnerClass {
// 定义静态与非静态成员都是可以的
private static int age = 22;
private int age2 = 22;
private void echo() {
// System.out.println(ab);
System.out.println(sab);// 只能访问外部类的静态成员
}
}
}

练习

1.根据注释填写(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
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}

static class Bean2{
public int J = 0;
}
}

class Bean{
class Bean3{
public int k = 0;
}
}

从前面可知,对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。

创建静态内部类对象的一般形式为:外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()

创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()

因此,(1),(2),(3)处的代码分别为:

1
2
3
4
5
6
7
Test test = new Test();    
Test.Bean1 bean1 = test.new Bean1();
---------------------------------------
Test.Bean2 b2 = new Test.Bean2();
Bean bean = new Bean();
---------------------------------------
Bean.Bean3 bean3 = bean.new Bean3();

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
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
outter.new Inner().print();
}
}


class Outter
{
private int a = 1;
class Inner {
private int a = 2;
public void print() {
int a = 3;
System.out.println("局部变量:" + a);
System.out.println("内部类变量:" + this.a);
System.out.println("外部类变量:" + Outter.this.a);
}
}
}

/*
局部变量:3
内部类变量:2
外部类变量:1
*/

关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:

  • 成员内部类的引用方式必须为 Outter.Inner
  • 构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。这段代码摘自《Java编程思想》
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class WithInner {
class Inner{

}
}
class InheritInner extends WithInner.Inner {

// InheritInner() 是不能通过编译的,一定要加上形参
InheritInner(WithInner wi) {
wi.super(); //必须有这句调用
}

public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}

抽象类

  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样

  • 抽象方法必须在抽象类中

    • 如果一个类包含抽象方法,那么该类必须是抽象类

    • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类

  • 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用

  • 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法

  • 在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口,接口可以多继承

  • 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。

  • 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能

  • 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法

  • 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类

接口

  • 普通类:只有具体实现
  • 抽象类:具体实现和规范(抽象方法)都有
  • 接口:只有规范!
  1. 是抽象方法的集合,接口通常以interface来声明;
  2. 一个类通过继承接口的方式,从而来继承接口的抽象方法;
  3. 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法;
  4. 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法;
  5. 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类;
  6. 在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象

接口与类相似点

  • 一个接口可以有多个方法;
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名;
  • 接口的字节码文件保存在 .class 结尾的文件中;
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别

  • 接口不能用于实例化对象;
  • 接口没有构造方法;
  • 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法;
  • 接口不能包含成员变量,除了 static 和 final 变量;
  • 接口不是被类继承了,而是要被类实现;
  • 接口支持多继承。

接口特性

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

抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行;
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法;
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
1
2
3
4
5
6
interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}

...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

重写接口中声明的方法时,需要注意以下规则

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常;
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型;
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法;

在实现接口的时候,也要注意一些规则

  • 一个类可以同时实现多个接口;
  • 一个类只能继承一个类,但是能实现多个接口;
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

标记接口

  • 最常用的继承接口是没有包含任何方法的接口;
  • 标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情;
  • 标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。

例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:

1
2
3
package java.util;
public interface EventListener
{}

没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:

  • 建立一个公共的父接口:

    正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

  • 向一个类添加数据类型:

    这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

值传递-引用传递

值传递

实参传递给形参的是值 形参和实参在内存上是两个独立的变量,对形参做任何修改不会影响实参

1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
int b =20;
change(b);// 实参 实际上的参数
System.out.println(b);
}
public static void change(int a){//形参 形式上的参数
a=100;
}
}

引用传递

实参传递给形参的是参数对于堆内存上的引用地址,实参和形参在内存上指向了同一块区域,对形参的修改会影响实参

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
public static void main(String[] args) {
int [] a={1,2,3};
System.out.println(a[0]);
change(a);
System.out.println(a[0]);
}
//实参和形参在内存上指向了同一块区域
public static void change(int[] a ){
a[0]=100; //形参的修改会影响实参
}
}

Java三大特性

封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;

要访问该类的代码和数据,必须通过严格的接口控制;

优点

  • 良好的封装能够减少耦合;
  • 类内部的结构可以自由修改;
  • 可以对成员变量进行更精确的控制;
  • 隐藏信息,实现细节。

实现步骤

  1. 修改属性的可见性来限制对属性的访问(一般限制为private)
  2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问(get、set方法)
  3. 采用 this 关键字是为了解决实例变量和局部变量之间发生的同名的冲突

继承

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类;

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

1
2
3
4
5
//类的继承格式
class 父类 {
}
class 子类 extends 父类 {
}

特性

  • 子类拥有父类非 private 的属性、方法;
  • final修饰的类,不能被继承;
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;
  • 子类可以用自己的方式实现父类的方法;
  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

关键字

super 与 this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类

this关键字:指向自己的引用

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 Animal {
void eat() {
System.out.println("animal : eat");
}
}

class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}

public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}

/*
输出结果为:
animal : eat
dog : eat
animal : eat
*/
  • super调用父类的构造方法,必须在构造方法的第一个
  • super必须只能出现在子类的方法或者构造方法中
  • super和this不能同时调用构造方法

多态

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作.

注意

  • 对象能执行哪些方法,主要看左边的类型,和右边关系不大
  • 父类可以指向子类,但是不能调用子类独有的方法

优点

  • 消除类型之间的耦合关系
  • 可替换性
  • 可扩充性
  • 接口性
  • 灵活性
  • 简化性

多态存在的三个必要条件

简单的多态实例

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
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("看家");
}
}

稍微复杂的多态

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class Employee {
private String name;
private String address;
private int number;
public Employee(String name, String address, int number) {
System.out.println("Employee 构造函数");
this.name = name;
this.address = address;
this.number = number;
}
public void mailCheck() {
System.out.println("邮寄支票给: " + this.name
+ " " + this.address);
}
public String toString() {
return name + " " + address + " " + number;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public void setAddress(String newAddress) {
address = newAddress;
}
public int getNumber() {
return number;
}
}
-------------------------------------------------------------
public class Salary extends Employee
{
private double salary; // 全年工资
public Salary(String name, String address, int number, double salary) {
super(name, address, number);
setSalary(salary);
}
public void mailCheck() {
System.out.println("Salary 类的 mailCheck 方法 ");
System.out.println("邮寄支票给:" + getName()
+ " ,工资为:" + salary);
}
public double getSalary() {
return salary;
}
public void setSalary(double newSalary) {
if(newSalary >= 0.0) {
salary = newSalary;
}
}
public double computePay() {
System.out.println("计算工资,付给:" + getName());
return salary/52;
}
}
-------------------------------------------------------------
public class VirtualDemo {
public static void main(String [] args) {
Salary s = new Salary("员工 A", "北京", 3, 3600.00);
Employee e = new Salary("员工 B", "上海", 2, 2400.00);
System.out.println("使用 Salary 的引用调用 mailCheck -- ");
s.mailCheck();
System.out.println("\n使用 Employee 的引用调用 mailCheck--");
e.mailCheck();
}
}
-------------------------------------------------------------
/*
Employee 构造函数
Employee 构造函数
使用 Salary 的引用调用 mailCheck --
Salary 类的 mailCheck 方法
邮寄支票给:员工 A ,工资为:3600.0

使用 Employee 的引用调用 mailCheck--
Salary 类的 mailCheck 方法
邮寄支票给:员工 B ,工资为:2400.0
*/

理解:

  1. 主函数初始化new Salary时,因为继承关系,所以执行父类构造方法,初始化两次,执行了两次
  2. s.mailCheck();正常调用类Salary中的方法
  3. e.mailCheck(); 父类的引用指向了子类的对象,因为类Salary重写了父类的方法,所以,调用类Salary中的方法

Java重写与重载

⚠️⚠️⚠️注意细节⚠️⚠️⚠️

类中方法加载顺序

静态代码块——匿名代码块——构造方法

字符输出问题

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ZiDongLeiZhuan{
public static void main(String[] args){
int a = 10;
int b = 20;
System.out.println("" + a + b);
System.out.println(a + b + "");
}
}
/*
运行结果
1020
30
*/

如果""在前 则字符串拼接!如果""在后 则为数学相加!

关于调用静态方法与非静态方法的区别

例子:注意看 输出是不同的

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
//主启动类
public class Application{
public static void main(String[] args){
//调用静态方法时 方法的调用只和 左边 定义的数据类型有关
A a = new A();
a.test();
//父类的引用指向了子类
B b = new A(); //子类重写了父类的方法 只和非静态方法有关
b.test()
}
}

---非静态方法---
//父类
public class B{
public void test(){
System.out.println("B=>test()");
}
}
//子类
public class A extends B{
public void test(){
System.out.println("A=>test()");
}
}
/*
A=>test()
A=>test()
*/
---静态方法---
//父类
public class B{
public static void test(){
System.out.println("B=>test()");
}
}
//子类
public class A extends B{
public static void test(){
System.out.println("A=>test()");
}
}
/*
A=>test()
B=>test()
*/

理解

静态方法属于类的方法,非静态方法属于对象的方法

调用静态方法时,b调用了B类的方法,因为b是B类定义的,只和 **左边** 定义的数据类型有关

调用非静态方法时,b调用的是对象的方法,而b这个对象是用A类new的,所以调用A的方法

Java程序初始化顺序

  1. 父类的静态变量
  2. 父类的静态代码块
  3. 子类的静态变量
  4. 子类的静态代码块
  5. 父类的非静态变量
  6. 父类的非静态代码块
  7. 父类的构造方法
  8. 子类的非静态变量
  9. 子类的非静态代码块
  10. 子类的构造方法
查看评论