Java基础
参考毕向东Java基础教程
1.自动类型转换(隐式类型转换)
-
两种类型是相互兼容的
-
目的数据类型的占用范围要一定要大于源类型
正向过程:由低字节向高字节自动转换,byte->short->int->long->float->double
2.强制类型转换(显式类型转换)
格式:目标类型 变量 = (目标类型)源类型变量/常量
逆向过程:使用强制转换,可能丢失精度。
3.数据类型自动提升
多种不同数据类型的表达式中,类型会自动向范围表示大的值的数据类型提升。
没有final修饰的变量相加后会被自动提升为int型。
4.程序流程控制
判断结构:
- if else与三元运算符的区别:三元运算符由于是运算符,因此必须有结果。
- Java语言中语句结束一般有两种方式:以分号 ; 或后大括号 } 结束,以 } 结束的一般为流程控制语句。
局部代码块
{
int m = 89;
System.out.println("Hello World!..."+m);
}
System.out.println("Over..."+m); //编译报错
局部代码块一执行完, m变量在内存中就会被释放。因此局部代码块可以定义局部变量的生命周期。为了节约内存空间,在确保后面不会在使用此变量的情况下,可以使用局部代码块的形式,执行完后,其空间就会被腾出来(虚拟机在内存当中开辟的空间是有限的,除非在启动虚拟机的时候设置需要的空间)。
选择结构
1.switch能判断的数据类型:
- 基本数据类型:byte, short, char, int
- 包装数据类型:Byte, Short, Character, Integer
- 枚举类型:Enum(Jdk 5+ 开始支持)
- 字符串类型:String(Jdk 7+ 开始支持)
2.
- 程序一执行到switch,会把大括号内所有的语句加载到内存中。
- switch中条件放置无顺序,但执行有顺序。
Example1:即使default放在第一位,也会先判断case
switch(表达式)
{
default:
执行语句;
break;
case 取值1:
执行语句;
break;
case 取值2:
执行语句;
break;
...
}
- 程序遇到满足条件的值后,如果没有break,则会继续向下执行,之后的case不再进行判断,直接执行里面的语句,直到遇到break或执行到大括号终结。
Example2
int x = 2;
swtich(x)
{
default:
System.out.println("d");
//break;
case 4:
System.out.println("a");
//break;
case 1:
System.out.println("b");
break;
case 3:
System.out.println("c");
break;
}
//输出结果为d, a, b
- Example3:多个值,对应相同执行语句
int month = 3;
switch(month)
{
case 3:
case 4:
System.out.println(month+"月对应的是春季");
break;
}
执行到case 3
,满足条件,但没有执行语句
和break
,继续往下执行并且不判断,执行完case 4中的语句再break。
if
和switch
的应用
- 对于几个固定的值判断,建议使用switch语句,因为其会将具体的答案都加载进内存,效率相对高一点。
- 常用的为if,不建议用switch语句,因为其功能性较差,且书写麻烦。
循环结构
while
与for
可以互换,区别在于for
为了循环而定义的变量在for
循环结束就在内存中释放,而while
循环使用的变量在循环结束后还可以继续使用。
5.函数
1、特点
- 函数只有被调用才会被执行
- 函数中只能调用函数,不可以在函数内部定义函数。
Example
class Demo
{
public static void main(String[] args)
{
System.out.println(add(3,5));
}
public static void add(int a, int b)
{
System.out.println(a + b);
return;
}
}
编译出错: System.out.println(add(3,5));
此处不允许使用void
类型。
2、函数的重载(overload)
概念:在同一类中,允许存在一个以上的同名函数,只要它们的参数个数或者参数类型不同即可。
特点:与返回值类型无关,只看参数列表。
好处:方便于阅读,优化了程序设计。
Example 1
public static int add(int a, int b)
{
return a+b;
}
public static double add(int a, int b)
{
return a+b;
}
编译报错:已在XX中定义add(int, int)
…,Java是严谨性语言,如果函数出现调用的不确定性,会编译失败。
Example 2: 打印乘法表
/**
打印指定参数的任意乘法表
*/
public static void printCFB(int num)
{
for(int x=1; x<=num; x++)
{
for(int y=1; y<=x; y++)
{
System.our.print(y+"*"+x+"="+y*x+"t");
}
System.out.println();
}
}
/**
打印标准乘法表(不指定参数的情况下)
*/
public static void printCFB()
{
for(int x=1; x<=9; x++)
{
for(int y=1; y<=x; y++)
{
System.our.print(y+"*"+x+"="+y*x+"t");
}
System.out.println();
}
}
如上,一般只要一重载,代码就会重复;可以通过调用的方式,减少重复代码。
/**
打印指定参数的任意乘法表
*/
public static void printCFB(int num)
{
for(int x=1; x<=num; x++)
{
for(int y=1; y<=x; y++)
{
System.our.print(y+"*"+x+"="+y*x+"t");
}
System.out.println();
}
}
/**
打印标准乘法表。
*/
public static void printCFB()
{
printCFB(9);
}
一般由于参数个数的原因重载,都能复用;而如果是参数类型,则不行。
6.数组
1、格式
/*=======第一种格式=======*/
元素类型[] 数组名 = new 元素类型[元素个数或数组长度];
/*=========示例========*/
int[] arr = new int[5];
/*=======第二种格式=======*/
元素类型[] 数组名 = new 元素类型[]{元素1, 元素2, ...};
/*=========示例========*/
int[] arr = new int[]{3,5,1,7}; //用new,常规初始化方式
int[] arr = {3,5,1,7}; //静态初始化方式
//以上两种方式在大部分情况下都是通用的,但它们在重新赋值或传参时会有一点小差别
Example
int[] arr = new int[3];
System.out.println(arr[0]);
System.out.println(arr[1]);
//均输出0(默认赋值为0)
2、数组的内存分配及特点
Java内存分为五片:
- 寄存器(CPU使用)
- 本地方法区(调用系统底层内容)
- 方法区(别名【数据区、共享区、share data】,存储方法)
- 栈内存(基本数据类型):存储的都是局部变量,变量所属的作用域一旦结束,该变量就会自动释放。
- 堆内存(引用数据类 ):存储数组和对象(其实数组也是对象),凡是new建立的对象都在堆中。
堆的特点:
- 每一个实体都有首地址值。
- 堆内存中的每一个变量都有默认初始化值,根据类型的不同而不一样,整数:0,小数:0.0或者0.0f,boolean:false,char :‘u0000’,实体:null。
- 垃圾回收机制:当堆中的实体没有被任何变量指向时,不像栈一样,会立刻被释放,而是会被视作垃圾,被自动回收,垃圾回收机制会不定时的,自动检测堆里的垃圾,进行回收。C++需要程序员手动回收堆里的垃圾,析构函数。
Example
int[] arr = new int[3];
//arr在栈中,new int[3]在堆中,堆中存的是实体,实体的用途:用于封装数据,存储很多数据
数组操作常见问题
Example 1: ArrayIndexOutOfBoundsException
异常
int[] arr = new int[3];
System.out.println(arr[3]);
编译完,不会出错,因为编译时不会在内存中建立数组,因此也不会找角标。
运行时,产生问题,当访问到数组中不存在的角标/索引时,就会产生ArrayIndexOutOfBoundsException
异常。
Example 2: NullPointerException
异常
int[] arr = new int[3];
arr = null;
System.out.println(arr[0]);
运行出错,当引用型变量没有任何实体指向时,还在用其操作实体,就会产生NullPointerException
异常。
数组常见操作
Example 1: 获取最值
class Demo
{
public static void main(String[] args)
{
int[] arr = {-34,-19,-11,-109,-3,-56};
int max = getMax(arr);
System.out.println("max="+max);
}
public static int getMax(int[] arr)
{
int max = arr[0];
for(int x=1; x<arr.length; x++)
{
if(arr[x]>max) max = arr[x];
}
return max;
}
}
注意max初始化的问题,若初始化为0,则 判断不出 最大值。
注意:
1、给定一个有序数组,若往该数组中存储一个元素,并保证数组仍然是有序的,那么这个元素存储的角标如何获取–>return min。
2、
int[] arr = {13,15,19,27,33,45,78,106};
Arrays.binarySearch(arr,45);
//若元素存在,返回元素所在的位置;
//若元素不存在,返回的是-插入点-1;之所以-1,是因为避免返回0,例如Arrays.binarySearch(arr,5)
Example 2: 进制转换
可以通过Integer类的方法直接将十进制数转换为对应的进制数:
System.out.println(Integer.toBinaryString(-6));
System.out.println(Integer.toHexString(5));
System.out.println(Integer.toOctalString(32));
7.二维数组
格式1:int[][] arr = new int[3][2];
- 定义了名称为arr的二维数组
- 二维数组中有3个一维数组
- 每一个一维数组中有2个元素
- 一维数组的名称为
arr[0]
,arr[1]
,arr[2]
- 给第一个一维数组1角标位赋值为78写法:
arr[0][1] = 78;
格式2:int[][] arr = new int[3][];
- 二维数组中有3个一维数组
- 每个一维数组都是默认初始化值null
- 可以对这三个一维数组分别进行初始化
arr[0] = new int[3];
arr[1] = new int[1];
arr[2] = new int[2];
格式3:
int[][] arr = {{3,1,7},{5,8,2,9},{4,6}};
int[][] arr = new int[3][2];
int[] arr[] = new int[3][2];
int arr[][] = new int[3][2];
Example1:直接打印二维数组
int[][] arr1 = new int[3][2];
System.out.println(arr); //[[I@c17164
System.out.println(arr[0]); //[I@1fb8ee3
System.out.println(arr[0][0]); //0
int[][] arr2 = new int[3][];
System.out.println(arr); //[[I@c17164
System.out.println(arr[0]); //null
System.out.println(arr[0][0]); //NullPointerException
Example2:数组长度
System.out.println(arr.length); //打印二维数组的长度-->就是一维数组的个数。
System.out.println(arr[1].length); //打印二维数组中角标为1的一维数组的长度。
8.继承
概述
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承单独的类即可。
- 多个类可以称为子类,单独的类称为父类或者超类。
- 通过extends关键字让类与类之间产生继承关系。
class SubDemo extends Demo{}
- 继承的好处:
继承的出现提高了代码的复用性。
继承的出现让类与类之间产生了关系,提供了多态的前提。
特点
- Java中支持单继承,不支持多继承
一个子类只能有一个直接父类,否则若多个父类中有相同成员,会产生调用的不确定性。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...{} //error
- Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
当要使用一个继承体系时:
- 查看该体系中的顶层类,了解该体系的基本功能
- 创建体系中的最子类对象,完成功能的使用
- 什么时候定义继承?
当类与类之间存在着所属(“is a”)关系的时候,就定义继承。如xxx是yyy的一种,则xxx extends yyy。或者判断父类中的功能是否子类都应该具备,若有些不具备,则不存在继承。不要仅为了获取其他类中某个功能而去继承 。
super关键字
this和super的用法很相似。
注意:两者都要求在构造方法的首行,所以两者不能同时使用
。
1、this
- this三大作用:调用属性,调用方法,利用this表示当前对象
- this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
this的用法在java中大体可以分为3种:
- 普通的直接引用: this相当于是指向当前对象本身。
- 形参与成员名字重名,用this来区分:
class Person {
private int age = 10;
public Person(){
System.out.println( "初始化年龄:" +age);
}
public int GetAge(int age){
this.age = age;
return this.age;
}
}
public class test1 {
public static void main(String[] args) {
Person Harry = new Person();
System.out.println( "Harry's age is " + Harry.GetAge( 12 ));
}
}
运行结果:
初始化年龄:10
Harry's age is 12
- 引用构造函数
这个和super放在一起讲,见下面。
2、super
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
super也有三种用法:
- 普通的直接引用
与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员。 - 子类中的成员变量或方法与父类中的成员变量或方法同名
class Country {
String name;
void value() {
name = "China";
}
}
class City extends Country {
String name;
void value() {
name = "Shanghai";
super.value(); //调用父类的方法
System.out.println(name);
System.out.println(super.name);
}
public static void main(String[] args) {
City c= new City();
c.value();
}
}
运行结果:
Shanghai
China
可以看到,这里既调用了父类的方法,也调用了父类的变量。若不调用父类方法value(),只调用父类变量name的话,则父类name值为默认值null
。
3.引用构造函数
super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
class Person {
public static void prt(String s) {
System.out.println(s);
}
Person() {
prt( "父类·无参数构造方法: " + "A Person." );
} //构造方法(1)
Person(String name) {
prt( "父类·含一个参数的构造方法: " + "A person's name is " + name);
} //构造方法(2)
}
public class Chinese extends Person {
Chinese() {
super (); // 调用父类构造方法(1)
prt( "子类·调用父类”无参数构造方法“: " + "A chinese coder." );
}
Chinese(String name) {
super (name); // 调用父类具有相同形参的构造方法(2)
prt( "子类·调用父类”含一个参数的构造方法“: " + "his name is " + name);
}
Chinese(String name, int age) {
this (name); // 调用具有相同形参的构造方法(3)
prt( "子类:调用子类具有相同形参的构造方法:his age is " + age);
}
public static void main(String[] args) {
Chinese cn = new Chinese();
cn = new Chinese( "codersai" );
cn = new Chinese( "codersai" , 18 );
}
}
运行结果:
父类·无参数构造方法: A Person.
子类·调用父类”无参数构造方法“: A chinese coder.
父类·含一个参数的构造方法: A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
父类·含一个参数的构造方法: A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
子类:调用子类具有相同形参的构造方法:his age is 18
注意: 父类中的私有内容
,子类是否具备——实际上子类对于父类中私有的成员变量是继承了的
,即定义子类对象时,在堆中存有父类的私有成员变量,只是不能直接
访问,所以确切地说,应该是子类中不能直接地访问父类中的私有内容。
函数覆盖(Override)
当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时,就使用覆盖操作完成。
注意:
- 父类中的私有方法不可以被覆盖。
- 在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名获取。
- 覆盖时,子类方法权限一定要
大于等于
父类方法权限。 - 静态只能覆盖静态,或被静态覆盖(非静态不能覆盖静态,静态不能覆盖非静态,否则会报编译错)。
子类的实例化过程
构造函数不会继承更不会覆盖
子类中所有的构造函数都会默认访问父类中的空参数的构造函数。
public class Father
{
public Father()
{
System.out.println("Father run");
}
}
class Son extends Father
{
public Son()
{
//super(); //默认会调用父类的空参构造函数
System.out.println("Son run");
}
}
- 当父类中没有空参数的构造函数时,子类的构造函数必须通过this或者super语句指定要访问的构造函数。
- 子类构造函数中至少有一个访问父类构造函数。
注意,类默认的空参构造函数的修饰符与类保持一致。
class Demo
{
/*
Demo()
{
super(); //父类为Object
return;
}
*/
}