java语法学习
一、java基本语法
1.配置环境变量
-
java程序执行过程:编写txt记事本文档,后缀名改为java,注意文件夹显示是否有隐藏后缀名;然后win+R+cmd打开窗口,修改路径(添加了path和classpath则分别不需cd到javac和文件所在路径),先javac+文件名.java将.java文件编译成.class文件,再java+class名执行。
-
path与classpath区别
path环境变量里面记录的是可执行文件,如.exe文件(没编译成.class文件前的文件),对可执行文件先在当前路径找,如果没有找到就去path环境变量中配置的路径中去找;
classpath环境变量记录的是java类的运行文件所在目录
-
通过JAVA_HOME配置环境变量:
(1) 新建系统变量JAVA_HOME,变量值填bin地址如C:\Program Files\Java\jdk-11.0.6\bin;
(2)在系统变量Path中添加%JAVA_HOME%\bin,则以后要更改、添加变量时便不需修改Path,直接修改JAVA_HOME中变量即可。若频繁修改Path,容易错改系统变量(system开头的变量)导致系统出问题!
2.标识符的命名规则
-
包:包其实就是文件夹,用于对相同的类名进行区分
全部小写
单级:rex
多级:rex.doc(用.分隔各级)
-
类或者接口:
一个单词:首字母大写 Student
多个单词:每个单词的首字母必须大写 HelloWorld
以下例外:DO/BO/DTO/VO/AO/PO/UID等,因本身就是首字母缩写,所以都大写;
-
方法或变量
一个单词:单词首字母小写
多个单词:从第二个单词开始,每个单词的首字母大写;
举例:studentAge,showAllNames
-
常量
一个单词:全部大写,如PI;
多个单词:每个字母都大写,用_隔开
举例:STUDENT_MAX_AGE;
-
代码中严禁拼音与英文混合方式表达,如getPingfenByName等;
-
代码中命名不能以下划线或美元符号开始或结束,如_name$。
3.接收用户输入
public static void main(String[] args){
//先new一个Scanner input
Scanner input= new Scanner(System.in);
//input.nextInt()只能接收int类型输入,相应的还有只能接收float等的;
System.out.println("请输入一段数字");
int i = input.nextInt();
//input.next()和input.nextLine()可以接收字符串输入,数字字母都行
System.out.println("请输入next接收的内容");
string str1 = input.next();
System.out.println("请输入nextLine接收的内容");
string str2 = input.nextLine();
}
-
input.next()和input.nextLine()区别:
input.next()空格、回车都可以结束输入,因此无法输入空格,若输入内容包括空格,则空格后内容都无法接收。若开头先输入空格,再输入字符,则前面的空格不会接收,接收字符。
但input.nextLine()指输入一行内容,只有回车才可以结束输入,因此可以接收空格。
4.数组
可以使用数组来表示“一组”int类型。
- 两种常用表示法:
int[] ages={10,11,12,13,14};
int[] ages2=new int[10];
代码如下:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
System.out.println(ns.length);//获取数组大小
}
}
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。
int[] ns = new int[] { 68, 79, 91, 85, 62 };
还可进一步简写为int[] ns = { 68, 79, 91, 85, 62 };
- 注意:数组是引用类型,并且数组大小不可变。
例1:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns;
ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 5
ns = new int[] { 1, 2, 3 };
System.out.println(ns.length); // 3
}
}
数组大小变了吗?看上去好像是变了,但其实根本没变。
对于数组ns来说,执行ns = new int[] { 68, 79, 91, 85, 62 };时,它指向一个5个元素的数组:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9s4lHkGW-1616302583389)(file:///C:\Users\jy\AppData\Local\Temp\ksohtml15336\wps6.jpg)]
执行ns = new int[] { 1, 2, 3 };时,它指向一个新的3个元素的数组:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7J8nswXU-1616302583395)(file:///C:\Users\jy\AppData\Local\Temp\ksohtml15336\wps7.jpg)]
但是,原有的5个元素的数组并没有改变,只是无法通过变量ns引用到它们而已。
例2:若数组元素不是基本类型,而是一个引用类型(string)
public class Main {
public static void main(String[] args) {
String[] names = {"ABC", "XYZ", "zoo"};
String s = names[1];
names[1] = "cat";
System.out.println(s); // s是"XYZ"还是"cat"?答案是XYZ。
}
}
- 多维数组
int[][]nums=new int[10][];
//赋值
nums[0]=new int[]{1,2,3};
//访问
System.out.println(nums[0][2]);//访问到了刚赋值的3
5.for each循环
for循环经常用来遍历数组,因为通过计数器可以根据索引来访问数组的每个元素:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
但是,很多时候,我们实际上真正想要访问的是数组每个元素的值。Java还提供了另一种for each循环,它可以更简单地遍历数组:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
和for循环相比,for each循环的变量n不再是计数器,而是直接对应到数组的每个元素。for each循环的写法也更简洁。但是,for each循环无法指定遍历顺序,也无法获取数组的索引。
除了数组外,for each循环能够遍历所有“可迭代”的数据类型,包括后面会介绍的List、Map等。
6.循环嵌套打印图形
- 注意若是双重循环来打印,外层循环控制行数,内层循环控制列数
- 例题:
7.break与continue区别
- break是终止本轮所有循环,之后所有循环终止;
- continue则是终止本轮单次的循环,本次循环continue后代码不执行,进入下一循环
8.如何退出多重循环
- 给想要结束的循环层次命名,break+循环名即可:
public class Demo10 {
/**
* 如何退出多重循环
* @param args
*/
public static void main(String[] args) {
haha:for(int i=0;i<10;i++) {//循环命名为haha
for(int j=0;j<10;j++) {
System.out.println("i="+i+" , j="+j);
if(i==5 && j==5) {
break haha;
}
}
}
}
}
二、面向对象
#初级
1.思想概述
-
面向对象(Object Oriented)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,是一种对现
实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向对象是相对于面向过程来讲的,指的是把 相关的数据和方法组织为一个整体 来看待,从更高的层次来进行系
统建模,更贴近事物的自然运行模式。
-
面向过程关注的是执行的过程,面向对象关注的是具备功能的对象。
面向过程到面向对象,是程序员思想上 从执行者到指挥者的转变。
举例:
我们再描述一个生活的场景: 场景:当我们独自生活时, 我们经常纠结一日三餐怎么吃。
面向过程: 每天亲力亲为: 买菜 - 做饭 - 吃饭 - 洗碗 的过程。
面向对象: 招聘一个保姆,每天等吃即可。
场景升级: 假设你是一个富豪, 拥有一座占地3000亩地的庄园 ,不再是只关注吃饭问题 , 还有花草树木修剪,泳池维 护清洗,卫生打扫,洗衣做饭。。。。。。
面向过程: 此处省略看着就累的N字。
面向对象: 招聘一个管家, 然后让管家招聘 园丁、泳池维护工、保姆等等。
结论:从上述的栗子中, 我们发现面向过程,我们需要关注很繁琐的过程。
而面向对象不用关注具体的细节,更关注的是统筹架构的问题。
其实我们进行大型应用开发时。就如上述的例子一样,如果我们写程序只关注过程的话,代码量达到一定层次以后,就很难再编写下去了。
-
三大思想
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象程序(Object Oriented Programming
-
三大特征
封装性:所有的内容对外部不可见
继承性:将其他的功能继承下来继续发展
多态性:方法的重载本身就是一个多态性的体现
2.创建对象
3.对象创建内存
java中需要关注的内存部分只要是栈和堆内存
- 栈
栈存储的是: 基本数据类型的数据,以及引用数据类型的引用(即地址)
例如: int a =10; Person p = new Person();
数据10存储在栈内存中 , 第二句代码创建的对象的引用(p)存在栈内存中
Java栈的区域很小,大概2m左右,
特点是存取的速度特别快 ,先进后出
堆存储速度快的原因: 栈内存, 通过'栈指针'来创建空间与释放空间!
指针向下移动,会创建新的内存,向上移动,会释放这些内存!
这种方式速度特别快,仅次于PC寄存器!
但是这种移动的方式, 必须要明确移动的大小与范围,明确大小与范围是为了方便指针的移动,
这是一个对于数据存储的限制,存储的数据大小是固定的,影响了程序的灵活性
所以我们把更大部分的数据,存储到了堆内存中
- 堆内存
堆内存存储的是:类的对象
.Java是一个纯面向对象语言, 限制了对象的创建方式: 所有类的对象都是通过new关键字创建
new关键字,是指告诉JVM 需要明确的去创建一个新的对象,去开辟一块新的堆内存空间:
堆内存与栈内存不同,优点在于我们创建对象时,不必关注堆内存中需要开辟多少存储空间,也不需要关注内存占用时长!
堆内存中内存的释放是由GC(垃圾回收器)完成的
垃圾回收器回收堆内存的规则: 当栈内存中不存在此对象的引用(即地址)时,则视其为垃圾,等待垃圾回收器回收!
例如:
Person p0 = new Person();
Person p1 = p0;
Person p2 = new Person();
对象创建内存变化图解:
对象内存释放图解:
4.构造方法
Person p = new Person();
在右侧Person后面出现的小括号, 其实就是在调用构造方法
- 特点:
在创建对象时,会自动调用,用于对象初始化。
所有的Java类中都会至少存在一个构造方法 如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代码。
- 注意
建议自定义无参构造方法,不要对编译器形成依赖,避免错误发生。
当类中有非常量成员变量时,建议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。
当类中所有成员变量都是常量或者没有成员变量时,建议不提供任何版本的构造。
格式与例子:
public class Demo4 {
public static void main(String[] args) {
//new的时候传参,则调用的是全参构造方法
Person2 p1 = new Person2("张三");
//p1.name = "张三";
p1.age = 18;
p1.say();
}
}
class Person2{
Person2(){//自己定义的 无参构造方法
}
Person2(String n){//自己定义的 全参构造方法
name = n;
}
String name;
int age;
void say() {
System.out.println("自我介绍。我是:"+name+" , 我的年龄:"+age);
}
}
5.重载
-
方法的重载
方法名称相同, 参数类型或参数长度不同, 可以完成方法的重载。方法的重载与返回值无关!
方法的重载 ,可以让我们在不同的需求下, 通过传递不同的参数调用方法来完成具体的功能。
-
构造方法的重载
一个类, 可以存在多个构造方法 :
参数列表的长度或类型不同即可完成构造方法的重载 ~
构造方法的重载 ,可以让我们在不同的创建对象的需求下, 调用不同的方法来完成对象的初始化!
6.匿名对象
public static void main(String[] args) {
int num = new Math2().sum(100, 200);
//若变量要使用第二次,必须使用非匿名:
//Math2 m1 = new Math();
System.out.println(num);
==========
#进阶
1.封装
对所有属性进行封装,就是定义成private,并为其提供setter及getter方法进行设置和取得操作。
好处是可以控制输入数值的合法性
创建Getters and Setter快捷键:用快捷键Alt+Shift+S,选择Generate Getters and Setter创建
class Person{
private String name ; // 表示姓名
private int age ; // 表示年龄
void tell(){
System.out.println("姓名:" + getName() + ";年龄:" + getAge()) ;
}
public void setName(String str){
name = str ;
}
public void setAge(int a){ //封装判断输入是否合法
if(a>0&&a<150)
age = a ;
}
public String getName(){
return name ;
}
public int getAge(){
return age ;
}
};
public class OODemo10{
public static void main(String args[]){
Person per = new Person();
per.setName("张三");
per.setAge(-30);
per.tell();
}
}
2.this用法
调用类中的方法时,若传的形参和类本身的属性名字相同,则给类自己的属性赋值时要加this,否则会形参自己传给自己
例:定义一个全参数构造方法
用this()调用构造方法,必须写在子类构造方法的第一行,与后文super()对比
//定义传全参数的构造方法
public Kkbstudent(String name,int age,String sex,String hobbies,String company,String subject) {
this.name = name;
this.age = age;
this.sex = sex;
this.hobbies = hobbies;
this.company = company;
this.subject = subject;
3.static静态
被static关键字修饰的方法或者变量,不需要依赖于对象来进行访问,在类加载时就加载并初始化了,可以通过类名去进行访问。
无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 )
在访问时: 静态不能访问非静态 , 非静态可以访问静态 !
应用:当多个对象的同一个属性,有相同的值时
public class StudentTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//若多个对象有相同的属性值,则定义为静态属性,并在new对象之前先赋值
Kkbstudent.company = "开课吧";
Kkbstudent.subject = "java学科";
Kkbstudent student1 = new Kkbstudent("张三",20,"男","打球");
Kkbstudent student2 = new Kkbstudent("李四",21,"女","打牌");
student1.detail();
student2.detail();
//若要创建共同属性不同的人
Kkbstudent student3 = new Kkbstudent("张三",20,"男","打牌","不开课了吧","打牌艺术");
student3.detail();
}
}
class Kkbstudent {
private String name;
private int age;
private String sex;
private String hobbies;
static String company;//静态属性定义
static String subject;
//定义传部分参数的构造方法
public Kkbstudent(String name,int age,String sex,String hobbies) {
this.name = name;
this.age = age;
this.sex = sex;
this.hobbies = hobbies;
}
//定义传全参数的构造方法
public Kkbstudent(String name,int age,String sex,String hobbies,String company,String subject) {
this.name = name;
this.age = age;
this.sex = sex;
this.hobbies = hobbies;
this.company = company;
this.subject = subject;
}
//......
4.代码块
-
普通代码块
在执行的流程中出现的代码块,我们称其为普通代码块。
-
构造代码块
在类中的成员代码块,我们称其为构造代码块,在每次对象创建时执行,执行在构造方法之前。
-
静态代码块
在类中使用static修饰的成员代码块,我们称其为静态代码块,在类加载时执行。每次程序启动到关闭 ,只会
执行一次的代码块。
-
同步代码块
在后续多线程技术中学习。
面试题:
构造方法与构造代码块以及静态代码块的执行顺序:
静态代码块 --> 构造代码块 --> 构造方法
public class ClothesTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Clothes.number=0;
Clothes clo1 = new Clothes("red");
clo1.detail();
Clothes clo2 = new Clothes("black");
clo2.detail();
Clothes clo3 = new Clothes("pink");
clo3.detail();
}
}
class Clothes{//定义Clothes类
private String color;
static int number;
//以下是静态代码块,随着类的加载执行(只第一次),因类只创建一次,故仅执行一次
//一般用于类内部共同的设置
static{
}
//以下为构造代码块,随着对象的每次创建执行一次,且执行在构造方法前,无论调用哪一个构造方法创建对象,都会执行!
//此处用来记录创建对象的数量
{
number++;
}
public Clothes(String color) {//构造方法
this.color = color;
}
void detail() {//定义detail方法显示衣服信息
System.out.println("衣服颜色:"+color+",衣服序号:"+number);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
==========
#高级
1.继承及子例实例化内存分析
继承:
class Student extends Person{
//......
}
子例实例化内存分析:
2.super
调用super,可以访问父类构造方法、属性、方法
调用super构造方法的代码,必须写在子类构造方法的第一行
this()与super()是不会同时出现在一个构造方法中。
例:
class Person{
//......
public Person(String name,int age){//父类的构造方法
this.name = name;
this.age = age;
//......
}
class Student extends Person{
public Student(){//子类构造方法
super("张三",20);//用super调用父类的构造方法
super.sex = "男";
}
}
3.重写、重载
- 重写应用场景:子继承父时,方法不适应子类,就把方法重写写一遍
- 重写(override)规则:
(1)参数列表必须完全与被重写方法的相同;
(2)返回类型必须完全与被重写方法的返回类型相同;
(3)访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected
(4)父类的成员方法只能被它的子类重写。
(5)声明为static和privete的方法不能被重写,但是能够被再次声明。
- 重写(override)、重载(overload)区别:
重载 | 重写 | |
---|---|---|
发生位置 | 一个类中 | 子父类中 |
参数列表限制 | 必须不同 | 必须相同 |
返回值类型 | 与返回值无关 | 返回值类型必须一致 |
访问权限 | 与访问权限无关 | 子方法的访问权限,必须不能小于父方法的权限 |
异常处理 | 与异常无关 | 异常范围可以更小,但是不能抛出新的异常 |
4.final
-
修饰属性、变量:
变量变成常量
final修饰的局部变量,只能赋值一次(可先声明后赋值)
final修饰的成员属性,必须在声明时赋值
全局常量public static final
- 常量的命名规范:
由1个或多个单词组成,单词与单词之间必须使用下划线隔开,单词中所有字母大写例如: SoL INSERT
- 常量的命名规范:
public class Demo1{
final int a = 0;//final修饰属性,必须加初始值
public static void main(String[] args){
final int a;//final修饰局部变量
a = 10;
a = 20;//再次赋值,会报错
}
}
-
修饰类:
不可被继承
-
修饰方法:
不能被子类重写
5.抽象类
public abstract Person{//抽象类
public abstract void say();//抽象方法
}
public class Student extends Person{//实现类1
public void say(){//实现类必须重写父类的抽象方法
System.out.println("我是学生,我是嘴炮王");
}
}
public class Nurse extends Person{//实现类2
@override//可通过右键创建类的时候,就选定父类,eclipse可自动重写父类的抽象方法
public void say(){
System.out.println("我是护士,我技术棒棒的");
}
}
抽象类相关问题:
-
抽象类中也是可以写具体的方法的;
-
抽象类不可直接用new来创建对象,但子类创建对象时,抽象父类也会被 JVM实例化;
-
抽象类不能使用final声明;
因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能;
-
抽象类能有构造方法;
而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法;
-
抽象类只能用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法),默认缺省为 public;
-
如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为 abstract类。
6.接口
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
定义格式:
interface 接口名称{
//全局常量
public static final int a = 0;
//接口类里前面几个词可省略,默认变量是全局变量,如下
int b = 0;
//抽象方法 ;
public abstract void say();
//方法不能有大括号,前面几个词也可省略,是默认的,如下
void say2();
}
- 接口和抽象类的区别
(1)抽象类要被子类继承,接口要被类实现。
(2)接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
(3)普通变量。
(4)抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
(5)抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
(6)接口不能有构造方法,但是抽象类可以有
7.多态
-
概念
多态:就是对象的多种表现形式,(多种体现形态)
-
多态的体现
对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来。
ps: 方法的重载 和 重写 也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。
重载: 一个类中方法的多态性体现
重写: 子父类中方法的多态性体现。
-
多态的使用:对象的类型转换
类似于基本数据类型的转换:
· 向上转型:将子类实例变为父类实例
|- 格式:父类 父类对象 = 子类实例 ;
· 向下转型:将父类实例变为子类实例
|- 格式:子类 子类对象 = **(子类)**父类实例 ;
类型转换例子:
public static void mian(String[] args){
Student a = new Student();
Nurse b = new Nurse();
Person p1 = a;
Person p2 = b;
//以上是向上转型,若调用p1、p2的方法会调用子类a、b的方法,如下:
p1.say();
p2.say();
Student a2 = (Student)p1;
Student a3 = (Student)p2;
//向下转型,需要加括号强制转型,类似变量类型转换;
//此处将p2(实际上是Nurse)转为Student,后边调用方法时会报错
a2.say();
a3.say();
}
疑问:上述的p.say()执行的是父类Person方法还是子类Student方法?
- 多态应用场景:不需更改原代码(67行,1214行),新增一个类Nurse,便可让p.say()执行新的功能。
8.instanceof使用
-
作用:
判断某个对象是否是指定类的实例,则可以使用instanceof关键字
-
格式:
实例化对象 instanceof 类 //此操作返回boolean类型的数据
-
接多态的例子,可用于判断传入类型是否正确:
代码待补充
9.toString
-
建议重写Object中的toString方法。
此方法的作用:返回对象的字符串表示形式。
Object的toString方法, 返回对象的内存地址
例:
代码待补充
快捷键构造toString方法:alt+shift+s
10.equals()
建议重写Object中的equals(Object obj)方法,此方法的作用:指示某个其他对象是否“等于”此对象。
也可快捷的创建alt+shitf+s
重写代码待补充
简化前代码:
11.内部类
用得很少,不是重点,仅大概了解,面试可能会问问题。
-
概念:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
-
分类:
(1)成员内部类
(2)局部内部类
(3)匿名内部类
(4)静态内部类
- 成员内部类
特点: 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问 的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
class Outer {
private double x = 0;
public Outer(double x) {
this.x = x;
}
class Inner { //内部类
public void say() {
System.out.println("x="+x);
}
}
}
外部使用成员内部类
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();
- 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或
者该作用域内。
注意:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
class Person{
public Person() {
}
}
class Man{
public Man(){
}
public People getPerson(){
class Student extends People{ //局部内部类
int age =0;
}
return new Student();
}
}
- 匿名内部类
匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:
new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一
个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐
式的。
在使用匿名内部类的过程中,我们需要注意如下几点:
(1)使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或
者实现一个接口。
(2)匿名内部类中是不能定义构造函数的。
(3)匿名内部类中不能存在任何的静态成员变量和静态方法。
(4)匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
(5)匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
(6)只能访问final型的局部变量
- 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员
变量或者方法.
格式:
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
12.包装类
在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思 想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型的包装类。
分类:
· Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
· Object:Character、Boolean都是Object的直接子类。
-
装箱和拆箱
将一个基本数据类型变为包装类,那么这样的操作称为装箱操作。
将一个包装类变为一个基本数据类型,这样的操作称为拆箱操作
装箱操作:
在JDK1.4之前 ,如果要想装箱,直接使用各个包装类的构造方法即可,例如:
int temp = 10 ; // 基本数据类型
Integer x = new Integer(temp) ; // 将基本数据类型变为包装类
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例如:
Float f = 10.3f ; // 自动装箱
float x = f ; // 自动拆箱
System.out.println(f * f) ; // 直接利用包装类完成
System.out.println(x * x) ; // 直接利用包装类完成
- 字符串转换
使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。
在Integer类中提供了以下的操作方法:
public static int parseInt(String s) :将String变为int型数据
在Float类中提供了以下的操作方法:
public static float parseFloat(String s) :将String变为Float
在Boolean 类中提供了以下操作方法:
public static boolean parseBoolean(String s) :将String变为boolean
....
....
13.可变参数
据需要自动传入任意个数的参数。
语法:
返回值类型 方法名称(数据类型…参数名称){
//参数在方法内部 , 以数组的形式来接收
}
- 注意:
可变参数只能出现在参数列表的最后。
例:
public static void main(String[] args){
System.out.println(sum(1));
System.out.println(sum(1,2));
System.out.println(sum(1,2,3));
System.out.println(sum(1,2,3,4));
}
//int...nums : 表示可变参数,调用时可传递0~n个数字
public static int sum(int...nums){
int n = 0;
for(int i=0;i<nums.length;i++){
n+=nums[i];;
}
return n;
}
==========
#异常处理
-
什么是异常:异常是在程序中导致程序中断运行的一种指令流。
-
IDEA中错误提示方法:在标红位置alt+回车,会显示解决方法,如try-catch、throw等
1.try-catch
- 处理与不处理的区别
不处理程序会崩溃,出问题的那行代码以后的都不会再执行,机理如下:
处理,则程序在执行try-catch后,继续执行后续指令,见例1:
package com.java.demo1;
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* 处理多异常的格式 1
*/
public class Demo2 {
public static void main(String[] args){
haha();
System.out.println("程序执行完毕 , 正常结束");//haha()中有异常处理后,后续指令可继续完成
}
private static void haha() {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字");
int x = input.nextInt();
System.out.println("请再输入一个数字");
int y = input.nextInt();
System.out.println(x / y);
System.out.println("处理完毕");
}catch(InputMismatchException e){
System.out.println("必须输入数字啊, 帅哥");
}catch(ArithmeticException e){
System.out.println("除数不能为0啊 , 帅哥");
}
}
}
由上述例子,try-catch格式:
try{
// 有可能发生异常的代码段
}catch(异常类型1 对象名1){
// 异常的处理操作
}catch(异常类型2 对象名2){
// 异常的处理操作
} ...
finally{
// 异常的统一出口
}
-
catch的异常处理类型,可根据运行报错时红字部分显示的类型获取,能进行try-catch的异常都是包含在Exception类中。
-
异常体系结构
异常指的是Exception , Exception类, 在Java中存在一个父类Throwable(可能的抛出)
Throwable存在两个子类:
(1)Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。
(2)Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch的处理。
-
RuntimeException即运行时异常,代码是不会标红线的;而受检异常代码会标红线,必须抛出或try-catch
-
更常用的异常处理方式:
上述例1,两种异常的父类都是RuntimeException,可用父类RuntimeException来代替,甚至是Exception父类,这也是多态的一种表现形式,如例2:
public class Demo4 {
public static void main(String[] args){
haha();
System.out.println("程序执行完毕 , 正常结束");
}
private static void haha() {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字");
int x = input.nextInt();
System.out.println("请再输入一个数字");
int y = input.nextInt();
System.out.println(x / y);
System.out.println("处理完毕");
}catch(Exception e){//多态
System.out.println("输入有误");
}finally {
//必然执行的异常统一处理出口
//无论是否发生异常, finally必然执行.
System.out.println("213123");
}
}
}
2.finally:
在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生 了异常,最终都要执行此段代码。
-
finally一般存放释放内存指令,因为放try或catch块不能确定是否发生异常,能不能执行。
-
finally——面试题高频题
-
finally什么时候执行:
若程序被关闭了,程序结束(非正常情况,电脑停电、程序关闭等)finally才不执行
-
try-catch-finally 中哪个部分可以省略?
答: catch和finally可以省略其中一个 , catch和finally不能同时省略
注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也不会这样去写代码
-
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答:finally中的代码会执行
详解:
执行流程:
(1)先计算返回值, 并将返回值存储起来, 等待返回
(2) 执行finally代码块
(3)将之前存储的返回值, 返回出去;
需注意:
(1)返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不会改变;
(2)finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或 catch中的值,若finally中也包含赋值语句,是否改变返回值要看数据类型,见下一部分单独说明;
(3)如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出JVM:System.exit(0)。
-
-
基本数据类型与引用数据类型
基本数据类型:如语句int a = 10,栈中存储的是指本身,然后return拷贝了一份值,finally改变的是拷贝前的值,最后返回值是拷贝值,故finally改不了;
引用数据类型:如Person p = new Person(),栈中存的是一个地址,return拷贝的也是地址,finally改变的是这个地址对应的堆内存里的值,也是拷贝的地址指向的值,故最后返回值被finally改变了;
3.throws
异常是否抛出去, 应该站在哪个角度思考?
如果是因为传参导致异常 , 应该通过throws将异常抛出去
import java.io.IOException;
public class Demo9 {
public static void main(String[] args) {
}
/**
* 异常是否抛出去, 应该站在哪个角度思考?
*
* 如果是因为传参导致异常 , 应该通过throws将异常抛出去.
*
* @param text
* @throws IOException : 因为传递的指令不对, 会导致此问题发生
*/
public static void shutdown(String text) throws IOException {
Runtime.getRuntime().exec(text);//指令只有这一条,不可能是程序的问题,是传入的内容的问题,所以throws出去
}
/**
* 此方法用于求两个参数的和
* 会将两个参数 转换为数字 求和
* @param s1 字符串参数1
* @param s2 字符串参数2
*/
public static void sum(String s1,String s2){
int sum = Integer.parseInt(s1)+Integer.parseInt(s2);
System.out.println("和是:"+sum);
}
}
4.throw
throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出
的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。
应用:比如摄入不合法后,比起强制设定一个默认值,用throw抛出更合理些,提醒程序员这里有异常,必须处理(比如再次输入)。
引用数据类型:如Person p = new Person(),栈中存的是一个地址,return拷贝的也是地址,finally改变的是这个地址对应的堆内存里的值,也是拷贝的地址指向的值,故最后返回值被finally改变了;
3.throws
异常是否抛出去, 应该站在哪个角度思考?
如果是因为传参导致异常 , 应该通过throws将异常抛出去
import java.io.IOException;
public class Demo9 {
public static void main(String[] args) {
}
/**
* 异常是否抛出去, 应该站在哪个角度思考?
*
* 如果是因为传参导致异常 , 应该通过throws将异常抛出去.
*
* @param text
* @throws IOException : 因为传递的指令不对, 会导致此问题发生
*/
public static void shutdown(String text) throws IOException {
Runtime.getRuntime().exec(text);//指令只有这一条,不可能是程序的问题,是传入的内容的问题,所以throws出去
}
/**
* 此方法用于求两个参数的和
* 会将两个参数 转换为数字 求和
* @param s1 字符串参数1
* @param s2 字符串参数2
*/
public static void sum(String s1,String s2){
int sum = Integer.parseInt(s1)+Integer.parseInt(s2);
System.out.println("和是:"+sum);
}
}
4.throw
throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出
的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。
应用:比如摄入不合法后,比起强制设定一个默认值,用throw抛出更合理些,提醒程序员这里有异常,必须处理(比如再次输入)。
代码: throw new Exception(“抛着玩的。”) ;