java面向对象
以类的方式组织代码,以对象的方式组织(封装)数据
三大特性:封装、继承、多态
1.对象声明
public class A {
int num=0;
//......
}
public class MainTest {
public static void main(String[] args) {
A x=new A();
}
}
new为x分配一个内存空间,A()构造一个对象,可以在A类中自定义A的构造器。
若类成员不加private/public关键字,将默认为public(C++中是private)
2.静态方法
//类A
public class A {
public static void staticFunction(){
System.out.println("This is a static function");
}
public void commonFunction(){
System.out.println("This is a common function");
}
}
//主类
public class MainTest {
public static void main(String[] args) {
A.staticFunction(); //静态方法可以通过类直接调用
A x=new A();
x.commonFunction(); //非静态方法必须通过实例调用
}
}
静态方法也可以通过实例调用,不过会产生警告
如:Static member ‘Mitchell.A.staticFunction()’ accessed via instance reference。 IDE会建议只通过类来调用静态方法,可能是考虑到实例会被回收。
3.构造器
//Student.class
public class Student {
private String id;
private String name;
private int age;
private int score;
public Student(String id,String name,int age,int score){
this.id=id;
this.name=name;
this.age=age;
this.score=score;
System.out.println("学生信息记入成功");
}
public String makeString(){
return "[ "+name+",学号:"+id+",年龄:"+age+",成绩:"+score+" ]";
}
}
public class HelloWorld {
public static void main(String[] args) {
Student a=new Student("123456789","张三",19,90);
System.out.println(a.makeString());
}
}
IDEA中通过alt+insert键可以快速生成构造器以及其他常用方法。
与C++的相同之处:
- 如果在已经定义了带参构造器的情况下还想调用无参构造器,则必须在类中写出无参构造器的实现。
与C++的不同之处:
- java不用手动管理内存(有垃圾回收机制),不用像C++一样写出析构函数(以及使用delete关键字)。
- java构造器中,若成员变量和使用的参数命名冲突,则必须调用this来获取成员变量(如上述Student类中"this.id=id")。C++对这种情况不会报错。
- 若类成员不加private/public关键字,java将默认为public,C++则默认为private。
堆栈调用图解
方法区属于堆
4.封装
目的:高内聚,低耦合。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合指仅暴露少量的方法给外部使用。
与C++一样,私有成员若要通过对象操作,需要额外定义一些方法。
IDEA使用alt+insert中的getter和setter选项可以直接生成对特定私有成员变量的最简操作方法。
5.继承
java中只有单继承。(C++允许多继承)
java中所有类都默认继承Object类,所以即使是一个空的类也会内含一些从Object继承过来的方法。
java通过extends关键字表示继承。IDEA中通过ctrl+H可以打开继承树。
5.1 super
this | super | |
---|---|---|
代表的对象不同 | 当前类的引用 | 当前类的父类的引用 |
前提 | 没有继承也可以使用 | 必须有继承条件才能使用(一般都能用,因为都会继承自Object类) |
构造方法 | this()本类的构造 | super()父类的构造 |
例:
//Person.java 父类
package Mitchell;
public class Person {
public String name="UNKNOW";
public int age=-1;
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//Student 子类
package Mitchell;
public class Student extends Person{
public Student(){
super(); //调用父类的无参构造方法
}
public Student(String name) {
this.name=name;
}
public Student(int age){
this.age=age;
}
/*public Student(String name, int age) { //这个方法与下一个方法效果相同
super(name, age);
}*/
public Student(String name,int age){
this(name); //调用当前类的第二个方法
this.age=age;
//this(age);
//this()只能调用一次,因为它必须放在第一句,如果用了两个或两种this(),会报错
}
}
//测试
package Mitchell;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
Student x=new Student("张三",18);
System.out.println("["+x.name+","+x.age+"]");
}
}
//输出 [张三,18]
注:
- super调用父类的构造方法,必须放在构造方法中的第一句。
- super必须只出现在子类的方法或构造方法中。
- super和this不能同时用在同一个构造方法中。
可参考博客:https://blog.csdn.net/lncsdn_123/article/details/79025525
5.2 方法重写
重写原因:父类的功能,子类不一定需要,或不一定适合子类使用。
@Override 表示重写,是一个有功能的注释。IDEA中可以通过alt+insert–>override进行方法重载。
例:
//B.java 父类
public class B{
public void test(){
System.out.println("B-->test()");
}
}
//A.java 子类
public class A extends B{
@Override
public void test(){
System.out.println("A-->test()");
}
}
//测试
public class MainTest{
public static void main(String[] args){
A a=new A();
a.test(); //调用A的test()
B b=new A(); //父类的引用指向了子类
b.test(); //本来会调用B的test,但是当前的b指向的是类A创建的对象,且test()方法被类A重载了。
}
}
//两个test都输出 "A-->test()"
重写方法注意:
- 方法名、参数列表必须完全相同。
- 修饰符:重载后范围可以扩大,但是不能缩小。 (范围:public>protected>default>private)
- 抛出的异常:重载后的异常范围可以缩小,但是不能扩大。如ClassNotFoundException<Exception
6.多态
同一方法可以根据发送对象的不同而采用多种不同的行为方式。
多态存在的条件:
- 有继承关系。
- 子类重写父类的方法。
- 父类引用指向子类的对象。
一个类的实例是确定的,但是指向这个实例的对象不是确定的。
假设Student继承了Person,则可以如下声明对象
Student s1=new Student();
Person s2=new Student(); //父类指向直接子类
Object s3=new Student(); //父类指向间接子类
//一个对象能使用哪些方法跟"="号左边的类型有关。
//如果同一个方法子类有重载,就会调用子类的方法,否则调用"="号左边类的相关方法。
父类不能直接调用子类独有的方法。
假设Student有一个方法 a(),而其父类Person没有。若父类对象要调用a(),可以如下表示
((Student)s2).a(); //把父类对象强制转换成子类对象
上述方式的强制转换只是临时的,如果接下来还想调用方法a()的话仍然要按上述方式。
当然还可以新建一个对象(假设为x)存储强制转换后的s2对象,这样x可以直接调用a()。
类型转换异常:ClassCastException。
带static、final、private关键字的方法不能被重写。
注:
- 父类引用可以指向子类的对象。
- 子类对象转换为父类对象为向上转型,可以自动执行,不过可能会丢失一些子类中的方法。
- 父类对象转换为子类对象为向下转型,需要执行强制转换。
7.static静态代码块
public class HelloWorld {
{
System.out.println("匿名代码块");
}
//1.最先执行,且只执行一次
static{
System.out.println("静态代码块");
}
//
public HelloWorld(){
System.out.println("构造方法");
}
public static void main(String[] args) {
HelloWorld x1=new HelloWorld();
System.out.println("================");
HelloWorld x2=new HelloWorld();
}
}
//输出:
//静态代码块
//匿名代码块
//构造方法
//================
//匿名代码块
//构造方法
8.抽象类
声明
public abstruct class a{
public abstruct void function(); //抽象方法
}
抽象方法通过继承在非抽象子类中进行具体实现。
注:
- 抽象类不能构建实例。不过抽象类中可以写构造器。
- 抽象类中可以写普通方法。
- 抽象方法只能写在抽象类中。
9.接口
接口可以多继承
接口可以算作特殊的抽象类,它的内部只有抽象方法。
声明
public interface UserServices{
//抽象方法或其他成员
}
接口中的抽象方法不用写public abstruct这些关键字。直接写函数定义即可,
如:void function(int x);
接口默认为抽象方式
接口中也可以定义常量,一般用public static final修饰,不过用处不大。
接口通过关键字implement实现。
public class UserServiceimp implements Uservices{ //可以同时实现多个接口
//......
}
10.内部类
更详细内容可参考博客:https://www.cnblogs.com/dolphin0520/p/3811445.html
10.1成员内部类
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;
private int x=10;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner { //内部类,可以访问外部类的private类型成员
private int b=-1; //内部类的private只能通过外部类访问,若为public则其他地方都可以直接访问
public Inner() {
}
}
}
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
10.2局部内部类
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类,当前的类只能访问getWoman()作用域内的资源
int age =0;
}
return new Woman();
}
}
局部内部类的访问仅限于方法内或者该作用域内。
局部内部类不能加private等关键字修饰它的成员。
10.3匿名内部类
这种类不需要提供类名就能直接实例化。而且只能用new来声明匿名内部类的定义
class PA {
public void display() {
System.out.println("在 A 类内部");
}
}
class Demo {
public void createClass() {
// 创建的匿名类继承了 A 类
A p1 = new A() { //匿名内部类的声明
public void display() {
System.out.println("在匿名类内部");
}
};
p1.display();
}
}
class Main {
public static void main(String[] args) {
Demo an = new Demo();
an.createClass();
}
}
匿名内部类不能是抽象类。
匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以初始化块,可以通过初始化块来完成构造器需要完成的工作。
10.4静态内部类
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
int a=10;
static int b=5;
public Outter() {
}
static class Inner { //静态内部类
public Inner() {
System.out.println(b); //可以访问外部类的static成员,非static成员(如a)不可访问
//外部类的非static成员必须依附于具体的对象
}
}
}
异常
异常的分类
- 检查性异常:用户的错误(或问题)而导致的异常,程序员无法预见这类异常。比如要打开一个不存在的文件时,会触发相关异常。
- 运行时异常:可能被程序员发现并避免。
- 错误(ERROR):错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略、例如,当栈溢出时,一个错误就发生了,他们在编译时也检查不到。
相关类
Java把异常当作对象来处理,并定义了一个积累java.lang.Throwable作为所有异常的超类。
Error
Error类对象由JVM生成并抛出,大多数错误与代码编写者所执行的操作无关。
JVM运行错误(Virtual MachineErroe),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError错误。这类错误发生时,JVM一般会选择终止线程。
还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、连接错误(LinkageError),这些错误不可查,因为他们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
Exception
常见的运行时异常(RuntimeException)
异常名 | 含义 |
---|---|
ArrayIndexOutOfBoundException | 数组下标越界 |
NullPointerException | 空指针异常 |
ArithmeticException | 算数异常 |
MissingResourceException | 丢失资源 |
ClassNotFoundException | 找不到类 |
1.捕获和抛出异常
相关关键字:try、catch、finally、throw、throws
try、catch、finally
例:
public class demo{
public static void main(){
int a=1;
int b=0;
try{ //监控可能会出现异常的区域
System.out.println(a/b); // 1/0会出现ArithmeticException异常,后两句会直接跳过进入catch
a++;
b++;
}
catch(ArithmeticException e){ //若捕获到相关异常,就执行catch中的语句
System.out.println("程序出现异常,b不能为0");
}
finally{ //finally中的语句一定会执行
System.out.println("finally");
}
}
}
上述代码中,ArithmeticException是Exception的子类,如果catch中定义的是Throwable或Exception对象,则所有异常都可以捕获。异常捕获结束之后程序仍旧会运行。
catch可以写多个,如下
try{
//......
}
catch(...){
//......
}
catch(...){
//......
}
..
finally{
//......
}
不过catch中的异常范围必须逐步增大,比如第一个catch()中写了Exception对象,那么第二个catch()中就不能写ArithmeticException等类似的异常对象,因为他们是Exception的子类,范围更小。
IDEA中可以通过ctrl+alt+T快速创建if-else、try-catch等结构语句。
throw、throws
throw可以主动抛出异常,这里的异常是一个实例
int a=1,b=0;
if(b==0) throw new ArithmeticException(); //使用throw会提前报错结束线程
System.out.println(a/b);
throws用在方法中。主要是声明这个方法会抛出相关类型的异常,使它的调用者知道要捕获这个异常。throws不会像throw一样直接输出错误信息。
import java.util.*;
public class MainTest {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int a,b;
while(in.hasNext()) {
a = in.nextInt();
b = in.nextInt();
System.out.println(MainTest.test(a, b));
}
in.close();
}
public static int test(int a,int b) throws ArithmeticException{
if(b==0){
throw new ArithmeticException();
}
return a/b;
}
}
效果
2.自定义异常
写一个类来继承Exception类即自定义异常
public class MyException extends Exception{
private int detail;
public MyException(int a){
this.detail=a;
}
@Override
public String toString(){
return "MyException["+" detail="+detail+" ]";
}
}
import java.util.*;
public class MainTest {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int a;
while(in.hasNext()) {
a = in.nextInt();
try{
test(a);
}catch (MyException e){
System.out.println("MyException-->"+e);
}
}
in.close();
}
public static void test(int a) throws MyException{
System.out.println("传入的参数为"+a);
if(a>10){
throw new MyException(a);
}
System.out.println("OK");
}
}
效果
注
- 处理运行时异常时,采用逻辑去合理规避的同时,辅助try-catch处理
- 在多重catch快后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
- 具体如何处理异常,要更具不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源